C# 線程手冊 第二章 .NET 中的線程 創建一個線程


我們將寫一個簡單的例子。對於我們為什么使用一個新的線程來說這不是一個好例子但是它將我們稍后要提到的復雜問題都去掉了。創建一個simple_thread.cs文件並把下面的代碼粘貼進去:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    class SimpleThread
    {
        void SimpleMethod()
        {
            int i = 5;
            int x = 10;
            int result = i * x;
            Console.WriteLine("This code calculated the value "
                + result.ToString() + " from thread ID: "
                + Thread.CurrentThread.ManagedThreadId);
        }

        static void Main(string[] args)
        {
            //Calling the method from our current thread.
            SimpleThread simpleThread = new SimpleThread();
            simpleThread.SimpleMethod();

            //Calling the method on a new thread.
            ThreadStart ts = new ThreadStart(simpleThread.SimpleMethod);
            Thread t = new Thread(ts);
            t.Start();
            Console.ReadLine();
        }
    }
}

現在保存,編譯然后運行。你的輸出會類似下面的結果:

0T9TP19~TNO{NMU[G)D5L22

我們繼續研究這個小例子以確定我們確實了解發生了什么。由於我們已經知道線程相關功能都被封裝到System.Threading命名空間中去了。所以我們必須首先將這個命名空間導入到我們的工程。一旦命名空間導入了,我們就可以在主線程中和新的工作線程中創建一個方法。我們使用SimpleMethod()方法:

void SimpleMethod()
{
    int i = 5;
    int x = 10;
    int result = i * x;
    Console.WriteLine("This code calculated the value "
        + result.ToString() + " from thread ID: "
        + Thread.CurrentThread.ManagedThreadId);
}

我們使用第一章介紹的AppDomain來找出正在運行哪個線程。這個方法不論何時執行,都顯示一個哪個線程在執行操作的報告。

我們的程序入口是Main()方法。首先會調用與主方法運行在同一個線程的SimpleMethod()方法。下一步很重要:我們第一次可以看看如何創建一個線程。在C#中創建一個線程之前,我們首先必須創建一個ThreadStart委托實例。委托是一個面向對象的類型安全的程序指針。由於我們將要告訴一個線程執行什么方法,所以我們必須將程序指針傳遞給線程的構造函數。我們的程序中會描述這個:

ThreadStart ts = new ThreadStart(simpleThread.SimpleMethod);

需要注意這里的方法名是不加括號的;它只是簡單的使用方法名。當我們創建完ThreadStart委托,我們接下來可以創建我們的線程來執行代碼。Thread唯一的構造函數將ThreadStart委托的實例作為參數。我們來看看代碼中是如何表示的:

Thread t = new Thread(ts);

我們定義了一個變量t作為新線程的名字。線程類的構造函數將ThreadStart委托作為它唯一的參數。

下一行代碼是Thread對象的Start()方法。通過調用我們傳給構造函數的ThreadStart委托,我們開始一個新執行線程。最后通過Console.ReadLine()來等待我們的鍵盤輸入:

t.Start();
Console.ReadLine();

額,我們已經創建了一個線程,但是僅僅這些還不能幫助我們洞悉線程的力量。事實上除了顯示不同的線程ID我們還沒有做別的。為了在一個更加真實的應用場景中看一下如何使用同樣的線程代碼,我們將創建另外一個模擬后台有一個長時間運行線程同時前台有另外一個線程的程序。在一個新文件do_something_thread.cs中創建一個新的控制台應用程序:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    public class DoSomethingThread
    {
        static void WorkerMethod()
        {
            for (int i = 1; i < 1000; i++)
            {
                Console.WriteLine("Worker Thread: " + i.ToString());
            }
        }

        static void Main()
        {
            ThreadStart ts = new ThreadStart(WorkerMethod);
            Thread t = new Thread(ts);
            t.Start();
            for (int i = 1; i < 1000; i++)
            {
                Console.WriteLine("Primary Thread: " + i.ToString());
            }
            Console.ReadLine();
        }
    }
}

輸出結果每時每刻都不同。線程執行在循環過程中會切換。最后的結果看起來會是這樣:

UNNFKZHOC32F7K%%]803}~0

由於上面的這段代碼並沒有介紹新技術所以咱們就略過。然而,需要注意在兩個線程在分享執行時間。任意一個線程都會被阻塞直到另外一個執行完。每個線程都被分配一個很短的時間來執行。在一個線程用完自己的執行時間后,下一個線程會在自己的時間片內開始執行。兩個線程互相交替直到執行結束。事實上,不是只有我們的兩個線程在交替並共享時間片。我們不是僅在我們的程序中交換時間片。現實是,我們與當前計算機中運行的很多線程共享執行時間。

ThreadStart 和 執行分支

我們再看一下之前提到的ThreadStart委托。我們可以使用這些委托做一些有意思的事情。舉個現實世界中的例子。假設你想在一個用戶運行某個程序時做一些后台的例行任務。不同的角色運行同樣程序會在后台執行不同的例行任務。例如,假設一個管理員運行了一個程序,你想在后台運行一個線程來收集報告數據並對數據進行格式化處理(做一個報表)。后台線程會在報表准備好的時候通知管理員。你可能不想對普通用戶也提供類似管理員的這種報表服務。這就是ThreadStart的面向對象功能有用的地方。

現在看一些簡單的例子。我們不會准確地模擬上面描述的場景,但是我們將演示如何依據ThreadStart定義的特定標准處理不同情況。創建一個應用程序以及一個新文件ThreadStartBranching.cs, 然后把下面代碼復制到新文件中:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    public class ThreadStartBranching
    {
        enum UserClass
        {
            ClassAdmin,
            ClassUser,
        }

        static void AdminMethod()
        {
            Console.WriteLine("Admin Method");
        }

        static void UserMethod()
        {
            Console.WriteLine("User Method");
        }

        static void ExecuteFor(UserClass uc)
        {
            ThreadStart ts;
            ThreadStart tsAdmin = new ThreadStart(AdminMethod);
            ThreadStart tsUser = new ThreadStart(UserMethod);

            if (uc == UserClass.ClassAdmin)
            {
                ts = tsAdmin;
            }
            else
            {
                ts = tsUser;
            }

            Thread t = new Thread(ts);
            t.Start();
        }

        static void Main()
        {
            //execute in the context of an admin user
            ExecuteFor(UserClass.ClassAdmin);

            //execute in the context of a regular user
            ExecuteFor(UserClass.ClassUser);

            Console.ReadLine();
        }
    }
}

代碼的輸出結果:

QRPV_AGMS@KOQI58ITGYQDB

我們將描述從上面代碼中得到的一些重點。首先,你將注意到我們創建了可能執行程序的用戶集合:

enum UserClass
{
    ClassAdmin,
    ClassUser,
}

接下來我們創建了兩個方法:AdminMethod() 和 UserMethod(). 這兩個方法會執行一系列指令並依據類型的不同而得到不同結果。我們這里只想確定它們已經運行了所以就簡單地把結果輸出到控制台:

static void AdminMethod()
{
    Console.WriteLine("Admin Method");
}

static void UserMethod()
{
    Console.WriteLine("User Method");
}

下一個你要注意的是在Execute()方法內我們聲明了一個叫做ts的變量作為一個ThreadStart類,但是沒有使用New關鍵字創建一個新實例。我們然后創建了兩個指向上面創建的不同方法的ThreadStart對象:

ThreadStart ts;
ThreadStart tsAdmin = new ThreadStart(AdminMethod);
ThreadStart tsUser = new ThreadStart(UserMethod);

所以,現在我們有了兩個ThreadStart對象和一個可以存儲ThreadStart實例的變量。然后我們使用If語句來讓我們的代碼形成分支並根據我們的商業邏輯為空的變量設置ThreadStart實例:

if (uc == UserClass.ClassAdmin)
{
    ts = tsAdmin;
}
else
{
    ts = tsUser;
}

最后,我們向我們的線程構造函數傳遞動態賦值的ThreadStart委托來創建一個線程,然后開始執行:

Thread t = new Thread(ts);
t.Start();
 

線程屬性和方法

我們在本章的開頭曾說過線程類有很多屬性和方法。我們承諾過使用System.Threading命名空間來控制線程執行會變得更加簡單。到目前為止,我們所做的就是創建線程然后啟動它們。

讓我們再看一兩個線程類的成員;Sleep()方法和IsAlive屬性。我們在第一章曾說過一個線程可能會在一段時間內進入睡眠狀態直到發生了時鍾中斷。讓一個線程進入睡眠狀態和調用靜態Sleep()方法一樣簡單。我們也說過我們可以確定一個線程的狀態。在下面的例子中我們將使用IsAlive屬性來確定一個線程是否已經執行完,使用Sleep()方法來暫停一個線程的執行。在下面的這段代碼中我們將演示如何使用這兩個成員:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    public class ThreadState
    {
        static void WorkerFunction()
        {
            string ThreadState;

            for (int i = 1; i < 50000; i++)
            {
                if (i % 5000 == 0)
                {
                    ThreadState = Thread.CurrentThread.ThreadState.ToString();
                    Console.WriteLine("Worker: " + ThreadState);
                }
            }
            Console.WriteLine("Worker Function Complete.");
        }

        static void Main()
        {
            string ThreadState;
            Thread t = new Thread(new ThreadStart(WorkerFunction));
            t.Start();

            while (t.IsAlive)
            {
                Console.WriteLine("Still waiting, I'm going back to sleep.");
                Thread.Sleep(200);
            }

            ThreadState = t.ThreadState.ToString();
            Console.WriteLine("He's finally done! Thread state is: " + ThreadState);
            Console.ReadLine();

        }
    }
}

你的輸出結果應該和下面顯示的類似(喜歡動手的可以自己將for 循環次數改一下,然后看看不同結果):

3AHTQR2{K9NTHE%~(Y56}AA

讓我們看一下我們首先使用新概念的Main()方法,我們創建了一個線程並將我們想讓委托執行的函數傳遞過去:

Thread t = new Thread(new ThreadStart(WorkerFunction));
t.Start();

注意不是創建一個變量來存儲我們的ThreadStart類,而是創建一個臨時變量並把它作為我們線程構造函數的參數。通常來說,由於處理器會進行線程切換所以我們的Main()方法會與新線程一起執行。然后我們使用新創建線程的IsAlive屬性來看它是否在執行。並將持續檢測這個變量。當工作線程活動時,主線程將持續睡眠200毫秒,然后醒來並再次測試工作線程是否是活動的:

while (t.IsAlive)
{
    Console.WriteLine("Still waiting, I'm going back to sleep.");
    Thread.Sleep(200);
}

下面我們想看看代碼中使用了兩次的ThreadState屬性。ThreadState實際上是一個返回枚舉類型的屬性。枚舉值會告訴你線程狀態。我們可以使用上一個例子中的if語句來判斷這個屬性也可以將它轉換成字符串格式並輸出到控制台:

ThreadState = t.ThreadState.ToString();
Console.WriteLine("He's finally done! Thread state is: " + ThreadState);
Console.ReadLine();

剩下是一些不需要看的標准代碼。要注意一些重要內容。首先是我們告訴一個線程睡眠一段時間以便於把執行時間放棄給其他線程。使用Thread對象的Sleep()方法-將我們想要線程睡眠的時間值傳遞進去。其次,我們可以使用IsAlive屬性來判斷線程是否執行完。最后,我們可以使用線程實例的ThreadState屬性來確定他們的精確狀態。

 

線程優先級

線程優先級確定每個線程相對其他線程的優先級。ThreadPriority枚舉定義了線程優先級的值。總共有以下幾個:

1. Highest

2. AboveNormal

3. Normal

4. BelowNormal

5. Lowest

當運行時創建一個線程且沒有給它設置任何優先級時,線程會采用默認的Normal 優先級。然而,這不能通過ThreadPriority枚舉改變。在看一下有關線程優先級的例子之前,我們來看看什么是線程優先級。我們創建一個簡單的線程例子來顯示線程的名字,狀態以及優先級信息,thread_priority.cs:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    public class ThreadPriority
    {
        public static Thread worker;
        static void Main()
        {
            Console.WriteLine("Entering void Main()");
            worker = new Thread(new ThreadStart(FindPriority));
            //Let's give a name to the thread.
            worker.Name = "FindPriority() Thread";
            worker.Start();
            Console.WriteLine("Exiting void Main()");
            Console.ReadLine();
        }

        public static void FindPriority()
        {
            Console.WriteLine("Name: " + worker.Name);
            Console.WriteLine("State: " + worker.ThreadState.ToString());
            Console.WriteLine("Priority: " + worker.Priority.ToString());
        }
    }
}

輸出結果如下:

S14T_EU06UVPB]7CS2LT]$Q

我們知道工作線程優先級是Normal。我們加一個新線程,然后以不同優先級調用同樣方法。thread_priority2.cs:

/*************************************
/* Copyright (c) 2012 Daniel Dong
 * 
 * Author:Daniel Dong
 * Blog:  www.cnblogs.com/danielWise
 * Email: guofoo@163.com
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SimpleThread
{
    public class Thread_priority2
    {
        public static Thread worker;
        public static Thread worker2;

        static void Main()
        {
            Console.WriteLine("Entering void Main()");
            worker = new Thread(new ThreadStart(FindPriority));
            worker2 = new Thread(new ThreadStart(FindPriority2));

            //Let's give a name to the thread
            worker.Name = "FindPriority() Thread";
            worker2.Name = "FindPriority() Thread 2";

            //Give the new thread object the highest priority
            worker2.Priority = ThreadPriority.Highest;
            worker.Start();
            worker2.Start();
            Console.WriteLine("Exiting void Main()");
            Console.ReadLine();
        }

        public static void FindPriority()
        {
            Console.WriteLine("Name: " + worker.Name);
            Console.WriteLine("State: " + worker.ThreadState.ToString());
            Console.WriteLine("Priority: " + worker.Priority.ToString());
        }

        public static void FindPriority2()
        {
            Console.WriteLine("Name: " + worker2.Name);
            Console.WriteLine("State: " + worker2.ThreadState.ToString());
            Console.WriteLine("Priority: " + worker2.Priority.ToString());
        }
    }
}

輸出結果類似下面:

_ACT`3~W[}C1R`@HS`}HV8I

線程基於使用Priority屬性設置的優先級來調度執行。每個操作系統對不同優先級的線程處理都不同且每個操作系統都可以改變線程優先級。

我們的應用程序沒有辦法禁止操作系統改變由程序員設置的線程優先級,這是因為操作系統是所有線程的管理者且它知道何時、如何調度它們。例如,線程優先級可以由操作系統基於以下幾個因素動態修改,比如用戶輸入等系統事件有較高優先級而內存緊缺將觸發垃圾回收機制。

下一篇我們將會介紹時鍾和回調…


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM