一、執行緒的定義
1. 1 程式、應用程式域與執行緒的關係
程式(Process)是Windows系統中的一個基本概念,它包含著一個執行程式所需要的資源。程式之間是相對獨立的,一個程式無法訪問另一個程式的資料(除非利用分散式計算方式),一個程式執行的失敗也不會影響其他程式的執行,Windows系統就是利用程式把工作劃分為多個獨立的區域的。程式可以理解為一個程式的基本邊界。
應用程式域(AppDomain)是一個程式執行的邏輯區域,它可以視為一個輕量級的程式,.NET的程式集正是在應用程式域中執行的,一個程式可以包含有多個應用程式域,一個應用程式域也可以包含多個程式集。在一個應用程式域中包含了一個或多個上下文context,使用上下文CLR就能夠把某些特殊物件的狀態放置在不同容器當中。
執行緒(Thread)是程式中的基本執行單元,在程式入口執行的第一個執行緒被視為這個程式的主執行緒。在.NET應用程式中,都是以Main()方法作為入口的,當呼叫此方法時系統就會自動建立一個主執行緒。執行緒主要是由CPU暫存器、呼叫棧和執行緒本地儲存器(Thread Local Storage,TLS)組成的。CPU暫存器主要記錄當前所執行執行緒的狀態,呼叫棧主要用於維護執行緒所呼叫到的記憶體與資料,TLS主要用於存放執行緒的狀態資訊。
程式、應用程式域、執行緒的關係如下圖,一個程式內可以包括多個應用程式域,也有包括多個執行緒,執行緒也可以穿梭於多個應用程式域當中。但在同一個時刻,執行緒只會處於一個應用程式域內。
由於本文是以介紹多執行緒技術為主題,對程式、應用程式域的介紹就到此為止。關於程式、執行緒、應用程式域的技術,在“C#綜合揭祕——細說程式、應用程式域與上下文”會有詳細介紹。
1.2 多執行緒
在單CPU系統的一個單位時間(time slice)內,CPU只能執行單個執行緒,執行順序取決於執行緒的優先順序別。如果在單位時間內執行緒未能完成執行,系統就會把執行緒的狀態資訊儲存到執行緒的本地儲存器(TLS) 中,以便下次執行時恢復執行。而多執行緒只是系統帶來的一個假像,它在多個單位時間內進行多個執行緒的切換。因為切換頻密而且單位時間非常短暫,所以多執行緒可被視作同時執行。
適當使用多執行緒能提高系統的效能,比如:在系統請求大容量的資料時使用多執行緒,把資料輸出工作交給非同步執行緒,使主執行緒保持其穩定性去處理其他問題。但需要注意一點,因為CPU需要花費不少的時間線上程的切換上,所以過多地使用多執行緒反而會導致效能的下降。
二、執行緒的基礎知識
2.1 System.Threading.Thread類
System.Threading.Thread是用於控制執行緒的基礎類,通過Thread可以控制當前應用程式域中執行緒的建立、掛起、停止、銷燬。
它包括以下常用公共屬性:
屬性名稱 | 說明 |
---|---|
CurrentContext | 獲取執行緒正在其中執行的當前上下文。 |
CurrentThread | 獲取當前正在執行的執行緒。 |
ExecutionContext | 獲取一個 ExecutionContext 物件,該物件包含有關當前執行緒的各種上下文的資訊。 |
IsAlive | 獲取一個值,該值指示當前執行緒的執行狀態。 |
IsBackground | 獲取或設定一個值,該值指示某個執行緒是否為後臺執行緒。 |
IsThreadPoolThread | 獲取一個值,該值指示執行緒是否屬於託管執行緒池。 |
ManagedThreadId | 獲取當前託管執行緒的唯一識別符號。 |
Name | 獲取或設定執行緒的名稱。 |
Priority | 獲取或設定一個值,該值指示執行緒的排程優先順序。 |
ThreadState | 獲取一個值,該值包含當前執行緒的狀態。 |
2.1.1 執行緒的識別符號
ManagedThreadId是確認執行緒的唯一識別符號,程式在大部分情況下都是通過Thread.ManagedThreadId來辨別執行緒的。而Name是一個可變值,在預設時候,Name為一個空值 Null,開發人員可以通過程式設定執行緒的名稱,但這只是一個輔助功能。
2.1.2 執行緒的優先順序別
.NET為執行緒設定了Priority屬性來定義執行緒執行的優先順序別,裡面包含5個選項,其中Normal是預設值。除非系統有特殊要求,否則不應該隨便設定執行緒的優先順序別。
成員名稱 | 說明 |
---|---|
Lowest | 可以將 Thread 安排在具有任何其他優先順序的執行緒之後。 |
BelowNormal | 可以將 Thread 安排在具有 Normal 優先順序的執行緒之後,在具有 Lowest 優先順序的執行緒之前。 |
Normal | 預設選擇。可以將 Thread 安排在具有 AboveNormal 優先順序的執行緒之後,在具有BelowNormal 優先順序的執行緒之前。 |
AboveNormal | 可以將 Thread 安排在具有 Highest 優先順序的執行緒之後,在具有 Normal 優先順序的執行緒之前。 |
Highest | 可以將 Thread 安排在具有任何其他優先順序的執行緒之前。 |
2.1.3 執行緒的狀態
通過ThreadState可以檢測執行緒是處於Unstarted、Sleeping、Running 等等狀態,它比 IsAlive 屬效能提供更多的特定資訊。
前面說過,一個應用程式域中可能包括多個上下文,而通過CurrentContext可以獲取執行緒當前的上下文。
CurrentThread是最常用的一個屬性,它是用於獲取當前執行的執行緒。
2.1.4 System.Threading.Thread的方法
Thread 中包括了多個方法來控制執行緒的建立、掛起、停止、銷燬,以後來的例子中會經常使用。
方法名稱 | 說明 |
---|---|
Abort() | 終止本執行緒。 |
GetDomain() | 返回當前執行緒正在其中執行的當前域。 |
GetDomainId() | 返回當前執行緒正在其中執行的當前域Id。 |
Interrupt() | 中斷處於 WaitSleepJoin 執行緒狀態的執行緒。 |
Join() | 已過載。 阻塞呼叫執行緒,直到某個執行緒終止時為止。 |
Resume() | 繼續執行已掛起的執行緒。 |
Start() | 執行本執行緒。 |
Suspend() | 掛起當前執行緒,如果當前執行緒已屬於掛起狀態則此不起作用 |
Sleep() | 把正在執行的執行緒掛起一段時間。 |
2.1.5 開發例項
以下這個例子,就是通過Thread顯示當前執行緒資訊
1 static void Main(string[] args)
2 {
3 Thread thread = Thread.CurrentThread;
4 thread.Name = "Main Thread";
5 string threadMessage = string.Format("Thread ID:{0}\n Current AppDomainId:{1}\n "+
6 "Current ContextId:{2}\n Thread Name:{3}\n "+
7 "Thread State:{4}\n Thread Priority:{5}\n",
8 thread.ManagedThreadId, Thread.GetDomainID(), Thread.CurrentContext.ContextID,
9 thread.Name, thread.ThreadState, thread.Priority);
10 Console.WriteLine(threadMessage);
11 Console.ReadKey();
12 }
執行結果
2.2 System.Threading 名稱空間
在System.Threading名稱空間內提供多個方法來構建多執行緒應用程式,其中ThreadPool與Thread是多執行緒開發中最常用到的,在.NET中專門設定了一個CLR執行緒池專門用於管理執行緒的執行,這個CLR執行緒池正是通過ThreadPool類來管理。而Thread是管理執行緒的最直接方式,下面幾節將詳細介紹有關內容。
類 | 說明 |
AutoResetEvent | 通知正在等待的執行緒已發生事件。無法繼承此類。 |
ExecutionContext | 管理當前執行緒的執行上下文。無法繼承此類。 |
Interlocked | 為多個執行緒共享的變數提供原子操作。 |
Monitor | 提供同步對物件的訪問的機制。 |
Mutex | 一個同步基元,也可用於程式間同步。 |
Thread | 建立並控制執行緒,設定其優先順序並獲取其狀態。 |
ThreadAbortException | 在對 Abort 方法進行呼叫時引發的異常。無法繼承此類。 |
ThreadPool | 提供一個執行緒池,該執行緒池可用於傳送工作項、處理非同步 I/O、代表其他執行緒等待以及處理計時器。 |
Timeout | 包含用於指定無限長的時間的常數。無法繼承此類。 |
Timer | 提供以指定的時間間隔執行方法的機制。無法繼承此類。 |
WaitHandle | 封裝等待對共享資源的獨佔訪問的作業系統特定的物件。 |
在System.Threading中的包含了下表中的多個常用委託,其中ThreadStart、ParameterizedThreadStart是最常用到的委託。
由ThreadStart生成的執行緒是最直接的方式,但由ThreadStart所生成並不受執行緒池管理。
而ParameterizedThreadStart是為非同步觸發帶引數的方法而設的,在下一節將為大家逐一細說。
委託 | 說明 |
---|---|
ContextCallback | 表示要在新上下文中呼叫的方法。 |
ParameterizedThreadStart | 表示在 Thread 上執行的方法。 |
ThreadExceptionEventHandler | 表示將要處理 Application 的 ThreadException 事件的方法。 |
ThreadStart | 表示在 Thread 上執行的方法。 |
TimerCallback | 表示處理來自 Timer 的呼叫的方法。 |
WaitCallback | 表示執行緒池執行緒要執行的回撥方法。 |
WaitOrTimerCallback | 表示當 WaitHandle 超時或終止時要呼叫的方法。 |
2.3 執行緒的管理方式
通過ThreadStart來建立一個新執行緒是最直接的方法,但這樣建立出來的執行緒比較難管理,如果建立過多的執行緒反而會讓系統的效能下載。有見及此,.NET為執行緒管理專門設定了一個CLR執行緒池,使用CLR執行緒池系統可以更合理地管理執行緒的使用。所有請求的服務都能執行於執行緒池中,當執行結束時執行緒便會迴歸到執行緒池。通過設定,能控制執行緒池的最大執行緒數量,在請求超出執行緒最大值時,執行緒池能按照操作的優先順序別來執行,讓部分操作處於等待狀態,待有執行緒迴歸時再執行操作。
基礎知識就為大家介紹到這裡,下面將詳細介紹多執行緒的開發。
三、以ThreadStart方式實現多執行緒
3.1 使用ThreadStart委託
這裡先以一個例子體現一下多執行緒帶來的好處,首先在Message類中建立一個方法ShowMessage(),裡面顯示了當前執行執行緒的Id,並使用Thread.Sleep(int ) 方法模擬部分工作。在main()中通過ThreadStart委託繫結Message物件的ShowMessage()方法,然後通過Thread.Start()執行非同步方法。
1 public class Message
2 {
3 public void ShowMessage()
4 {
5 string message = string.Format("Async threadId is :{0}",
6 Thread.CurrentThread.ManagedThreadId);
7 Console.WriteLine(message);
8
9 for (int n = 0; n < 10; n++)
10 {
11 Thread.Sleep(300);
12 Console.WriteLine("The number is:" + n.ToString());
13 }
14 }
15 }
16
17 class Program
18 {
19 static void Main(string[] args)
20 {
21 Console.WriteLine("Main threadId is:"+
22 Thread.CurrentThread.ManagedThreadId);
23 Message message=new Message();
24 Thread thread = new Thread(new ThreadStart(message.ShowMessage));
25 thread.Start();
26 Console.WriteLine("Do something ..........!");
27 Console.WriteLine("Main thread working is complete!");
28
29 }
30 }
請注意執行結果,在呼叫Thread.Start()方法後,系統以非同步方式執行Message.ShowMessage(),而主執行緒的操作是繼續執行的,在Message.ShowMessage()完成前,主執行緒已完成所有的操作。
3.2 使用ParameterizedThreadStart委託
ParameterizedThreadStart委託與ThreadStart委託非常相似,但ParameterizedThreadStart委託是面向帶引數方法的。注意ParameterizedThreadStart 對應方法的引數為object,此引數可以為一個值物件,也可以為一個自定義物件。
1 public class Person
2 {
3 public string Name
4 {
5 get;
6 set;
7 }
8 public int Age
9 {
10 get;
11 set;
12 }
13 }
14
15 public class Message
16 {
17 public void ShowMessage(object person)
18 {
19 if (person != null)
20 {
21 Person _person = (Person)person;
22 string message = string.Format("\n{0}'s age is {1}!\nAsync threadId is:{2}",
23 _person.Name,_person.Age,Thread.CurrentThread.ManagedThreadId);
24 Console.WriteLine(message);
25 }
26 for (int n = 0; n < 10; n++)
27 {
28 Thread.Sleep(300);
29 Console.WriteLine("The number is:" + n.ToString());
30 }
31 }
32 }
33
34 class Program
35 {
36 static void Main(string[] args)
37 {
38 Console.WriteLine("Main threadId is:"+Thread.CurrentThread.ManagedThreadId);
39
40 Message message=new Message();
41 //繫結帶引數的非同步方法
42 Thread thread = new Thread(new ParameterizedThreadStart(message.ShowMessage));
43 Person person = new Person();
44 person.Name = "Jack";
45 person.Age = 21;
46 thread.Start(person); //啟動非同步執行緒
47
48 Console.WriteLine("Do something ..........!");
49 Console.WriteLine("Main thread working is complete!");
50
51 }
52 }
執行結果:
3.3 前臺執行緒與後臺執行緒
注意以上兩個例子都沒有使用Console.ReadKey(),但系統依然會等待非同步執行緒完成後才會結束。這是因為使用Thread.Start()啟動的執行緒預設為前臺執行緒,而系統必須等待所有前臺執行緒執行結束後,應用程式域才會自動解除安裝。
在第二節曾經介紹過執行緒Thread有一個屬性IsBackground,通過把此屬性設定為true,就可以把執行緒設定為後臺執行緒!這時應用程式域將在主執行緒完成時就被解除安裝,而不會等待非同步執行緒的執行。
3.4 掛起執行緒
為了等待其他後臺執行緒完成後再結束主執行緒,就可以使用Thread.Sleep()方法。
1 public class Message
2 {
3 public void ShowMessage()
4 {
5 string message = string.Format("\nAsync threadId is:{0}",
6 Thread.CurrentThread.ManagedThreadId);
7 Console.WriteLine(message);
8 for (int n = 0; n < 10; n++)
9 {
10 Thread.Sleep(300);
11 Console.WriteLine("The number is:" + n.ToString());
12 }
13 }
14 }
15
16 class Program
17 {
18 static void Main(string[] args)
19 {
20 Console.WriteLine("Main threadId is:"+
21 Thread.CurrentThread.ManagedThreadId);
22
23 Message message=new Message();
24 Thread thread = new Thread(new ThreadStart(message.ShowMessage));
25 thread.IsBackground = true;
26 thread.Start();
27
28 Console.WriteLine("Do something ..........!");
29 Console.WriteLine("Main thread working is complete!");
30 Console.WriteLine("Main thread sleep!");
31 Thread.Sleep(5000);
32 }
33 }
執行結果如下,此時應用程式域將在主執行緒執行5秒後自動結束
但系統無法預知非同步執行緒需要執行的時間,所以用通過Thread.Sleep(int)阻塞主執行緒並不是一個好的解決方法。有見及此,.NET專門為等待非同步執行緒完成開發了另一個方法thread.Join()。把上面例子中的最後一行Thread.Sleep(5000)修改為 thread.Join() 就能保證主執行緒在非同步執行緒thread執行結束後才會終止。
3.5 Suspend 與 Resume (慎用)
Thread.Suspend()與 Thread.Resume()是在Framework1.0 就已經存在的老方法了,它們分別可以掛起、恢復執行緒。但在Framework2.0中就已經明確排斥這兩個方法。這是因為一旦某個執行緒佔用了已有的資源,再使用Suspend()使執行緒長期處於掛起狀態,當在其他執行緒呼叫這些資源的時候就會引起死鎖!所以在沒有必要的情況下應該避免使用這兩個方法。
3.6 終止執行緒
若想終止正在執行的執行緒,可以使用Abort()方法。在使用Abort()的時候,將引發一個特殊異常 ThreadAbortException 。
若想線上程終止前恢復執行緒的執行,可以在捕獲異常後 ,在catch(ThreadAbortException ex){...} 中呼叫Thread.ResetAbort()取消終止。
而使用Thread.Join()可以保證應用程式域等待非同步執行緒結束後才終止執行。
1 static void Main(string[] args)
2 {
3 Console.WriteLine("Main threadId is:" +
4 Thread.CurrentThread.ManagedThreadId);
5
6 Thread thread = new Thread(new ThreadStart(AsyncThread));
7 thread.IsBackground = true;
8 thread.Start();
9 thread.Join();
10
11 }
12
13 //以非同步方式呼叫
14 static void AsyncThread()
15 {
16 try
17 {
18 string message = string.Format("\nAsync threadId is:{0}",
19 Thread.CurrentThread.ManagedThreadId);
20 Console.WriteLine(message);
21
22 for (int n = 0; n < 10; n++)
23 {
24 //當n等於4時,終止執行緒
25 if (n >= 4)
26 {
27 Thread.CurrentThread.Abort(n);
28 }
29 Thread.Sleep(300);
30 Console.WriteLine("The number is:" + n.ToString());
31 }
32 }
33 catch (ThreadAbortException ex)
34 {
35 //輸出終止執行緒時n的值
36 if (ex.ExceptionState != null)
37 Console.WriteLine(string.Format("Thread abort when the number is: {0}!",
38 ex.ExceptionState.ToString()));
39
40 //取消終止,繼續執行執行緒
41 Thread.ResetAbort();
42 Console.WriteLine("Thread ResetAbort!");
43 }
44
45 //執行緒結束
46 Console.WriteLine("Thread Close!");
47 }
執行結果如下
四、CLR執行緒池的工作者執行緒
4.1 關於CLR執行緒池
使用ThreadStart與ParameterizedThreadStart建立新執行緒非常簡單,但通過此方法建立的執行緒難於管理,若建立過多的執行緒反而會影響系統的效能。
有見及此,.NET引入CLR執行緒池這個概念。CLR執行緒池並不會在CLR初始化的時候立刻建立執行緒,而是在應用程式要建立執行緒來執行任務時,執行緒池才初始化一個執行緒。執行緒的初始化與其他的執行緒一樣。在完成任務以後,該執行緒不會自行銷燬,而是以掛起的狀態返回到執行緒池。直到應用程式再次向執行緒池發出請求時,執行緒池裡掛起的執行緒就會再度啟用執行任務。這樣既節省了建立執行緒所造成的效能損耗,也可以讓多個任務反覆重用同一執行緒,從而在應用程式生存期內節約大量開銷。
注意:通過CLR執行緒池所建立的執行緒總是預設為後臺執行緒,優先順序數為ThreadPriority.Normal。
4.2 工作者執行緒與I/O執行緒
CLR執行緒池分為工作者執行緒(workerThreads)與I/O執行緒 (completionPortThreads) 兩種,工作者執行緒是主要用作管理CLR內部物件的運作,I/O(Input/Output) 執行緒顧名思義是用於與外部系統交換資訊,IO執行緒的細節將在下一節詳細說明。
通過ThreadPool.GetMax(out int workerThreads,out int completionPortThreads )和 ThreadPool.SetMax( int workerThreads, int completionPortThreads)兩個方法可以分別讀取和設定CLR執行緒池中工作者執行緒與I/O執行緒的最大執行緒數。在Framework2.0中最大執行緒預設為25*CPU數,在Framewok3.0、4.0中最大執行緒數預設為250*CPU數,在近年 I3,I5,I7 CPU出現後,執行緒池的最大值一般預設為1000、2000。
若想測試執行緒池中有多少的執行緒正在投入使用,可以通過ThreadPool.GetAvailableThreads( out int workerThreads,out int completionPortThreads ) 方法。
使用CLR執行緒池的工作者執行緒一般有兩種方式,一是直接通過 ThreadPool.QueueUserWorkItem() 方法,二是通過委託,下面將逐一細說。
4.3 通過QueueUserWorkItem啟動工作者執行緒
ThreadPool執行緒池中包含有兩個靜態方法可以直接啟動工作者執行緒:
一為 ThreadPool.QueueUserWorkItem(WaitCallback)
二為 ThreadPool.QueueUserWorkItem(WaitCallback,Object)
先把WaitCallback委託指向一個帶有Object引數的無返回值方法,再使用 ThreadPool.QueueUserWorkItem(WaitCallback) 就可以非同步啟動此方法,此時非同步方法的引數被視為null 。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 //把CLR執行緒池的最大值設定為1000
6 ThreadPool.SetMaxThreads(1000, 1000);
7 //顯示主執行緒啟動時執行緒池資訊
8 ThreadMessage("Start");
9 //啟動工作者執行緒
10 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback));
11 Console.ReadKey();
12 }
13
14 static void AsyncCallback(object state)
15 {
16 Thread.Sleep(200);
17 ThreadMessage("AsyncCallback");
18 Console.WriteLine("Async thread do work!");
19 }
20
21 //顯示執行緒現狀
22 static void ThreadMessage(string data)
23 {
24 string message = string.Format("{0}\n CurrentThreadId is {1}",
25 data, Thread.CurrentThread.ManagedThreadId);
26 Console.WriteLine(message);
27 }
28 }
執行結果
使用 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 方法可以把object物件作為引數傳送到回撥函式中。
下面例子中就是把一個string物件作為引數傳送到回撥函式當中。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 //把執行緒池的最大值設定為1000
6 ThreadPool.SetMaxThreads(1000, 1000);
7
8 ThreadMessage("Start");
9 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncCallback),"Hello Elva");
10 Console.ReadKey();
11 }
12
13 static void AsyncCallback(object state)
14 {
15 Thread.Sleep(200);
16 ThreadMessage("AsyncCallback");
17
18 string data = (string)state;
19 Console.WriteLine("Async thread do work!\n"+data);
20 }
21
22 //顯示執行緒現狀
23 static void ThreadMessage(string data)
24 {
25 string message = string.Format("{0}\n CurrentThreadId is {1}",
26 data, Thread.CurrentThread.ManagedThreadId);
27 Console.WriteLine(message);
28 }
29 }
執行結果
通過ThreadPool.QueueUserWorkItem啟動工作者執行緒雖然是方便,但WaitCallback委託指向的必須是一個帶有Object引數的無返回值方法,這無疑是一種限制。若方法需要有返回值,或者帶有多個引數,這將多費周折。有見及此,.NET提供了另一種方式去建立工作者執行緒,那就是委託。
4.4 委託類
使用CLR執行緒池中的工作者執行緒,最靈活最常用的方式就是使用委託的非同步方法,在此先簡單介紹一下委託類。
當定義委託後,.NET就會自動建立一個代表該委託的類,下面可以用反射方式顯示委託類的方法成員(對反射有興趣的朋友可以先參考一下“.NET基礎篇——反射的奧妙”)
1 class Program
2 {
3 delegate void MyDelegate();
4
5 static void Main(string[] args)
6 {
7 MyDelegate delegate1 = new MyDelegate(AsyncThread);
8 //顯示委託類的幾個方法成員
9 var methods=delegate1.GetType().GetMethods();
10 if (methods != null)
11 foreach (MethodInfo info in methods)
12 Console.WriteLine(info.Name);
13 Console.ReadKey();
14 }
15 }
委託類包括以下幾個重要方法
1 public class MyDelegate:MulticastDelegate
2 {
3 public MyDelegate(object target, int methodPtr);
4 //呼叫委託方法
5 public virtual void Invoke();
6 //非同步委託
7 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);
8 public virtual void EndInvoke(IAsyncResult result);
9 }
當呼叫Invoke()方法時,對應此委託的所有方法都會被執行。而BeginInvoke與EndInvoke則支援委託方法的非同步呼叫,由BeginInvoke啟動的執行緒都屬於CLR執行緒池中的工作者執行緒,在下面將詳細說明。
4.5 利用BeginInvoke與EndInvoke完成非同步委託方法
首先建立一個委託物件,通過IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state) 非同步呼叫委託方法,BeginInvoke 方法除最後的兩個引數外,其它引數都是與方法引數相對應的。通過 BeginInvoke 方法將返回一個實現了 System.IAsyncResult 介面的物件,之後就可以利用EndInvoke(IAsyncResult ) 方法就可以結束非同步操作,獲取委託的執行結果。
1 class Program
2 {
3 delegate string MyDelegate(string name);
4
5 static void Main(string[] args)
6 {
7 ThreadMessage("Main Thread");
8
9 //建立委託
10 MyDelegate myDelegate = new MyDelegate(Hello);
11 //非同步呼叫委託,獲取計算結果
12 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
13 //完成主執行緒其他工作
14 .............
15 //等待非同步方法完成,呼叫EndInvoke(IAsyncResult)獲取執行結果
16 string data=myDelegate.EndInvoke(result);
17 Console.WriteLine(data);
18
19 Console.ReadKey();
20 }
21
22 static string Hello(string name)
23 {
24 ThreadMessage("Async Thread");
25 Thread.Sleep(2000); //虛擬非同步工作
26 return "Hello " + name;
27 }
28
29 //顯示當前執行緒
30 static void ThreadMessage(string data)
31 {
32 string message = string.Format("{0}\n ThreadId is:{1}",
33 data,Thread.CurrentThread.ManagedThreadId);
34 Console.WriteLine(message);
35 }
36 }
執行結果
4.6 善用IAsyncResult
在以上例子中可以看見,如果在使用myDelegate.BeginInvoke後立即呼叫myDelegate.EndInvoke,那在非同步執行緒未完成工作以前主執行緒將處於阻塞狀態,等到非同步執行緒結束獲取計算結果後,主執行緒才能繼續工作,這明顯無法展示出多執行緒的優勢。此時可以好好利用IAsyncResult 提高主執行緒的工作效能,IAsyncResult有以下成員:
1 public interface IAsyncResult
2 {
3 object AsyncState {get;} //獲取使用者定義的物件,它限定或包含關於非同步操作的資訊。
4 WailHandle AsyncWaitHandle {get;} //獲取用於等待非同步操作完成的 WaitHandle。
5 bool CompletedSynchronously {get;} //獲取非同步操作是否同步完成的指示。
6 bool IsCompleted {get;} //獲取非同步操作是否已完成的指示。
7 }
通過輪詢方式,使用IsCompleted屬性判斷非同步操作是否完成,這樣在非同步操作未完成前就可以讓主執行緒執行另外的工作。
1 class Program
2 {
3 delegate string MyDelegate(string name);
4
5 static void Main(string[] args)
6 {
7 ThreadMessage("Main Thread");
8
9 //建立委託
10 MyDelegate myDelegate = new MyDelegate(Hello);
11 //非同步呼叫委託,獲取計算結果
12 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
13 //在非同步執行緒未完成前執行其他工作
14 while (!result.IsCompleted)
15 {
16 Thread.Sleep(200); //虛擬操作
17 Console.WriteLine("Main thead do work!");
18 }
19 string data=myDelegate.EndInvoke(result);
20 Console.WriteLine(data);
21
22 Console.ReadKey();
23 }
24
25 static string Hello(string name)
26 {
27 ThreadMessage("Async Thread");
28 Thread.Sleep(2000);
29 return "Hello " + name;
30 }
31
32 static void ThreadMessage(string data)
33 {
34 string message = string.Format("{0}\n ThreadId is:{1}",
35 data,Thread.CurrentThread.ManagedThreadId);
36 Console.WriteLine(message);
37 }
38 }
執行結果:
除此以外,也可以使用WailHandle完成同樣的工作,WaitHandle裡面包含有一個方法WaitOne(int timeout),它可以判斷委託是否完成工作,在工作未完成前主執行緒可以繼續其他工作。執行下面程式碼可得到與使用 IAsyncResult.IsCompleted 同樣的結果,而且更簡單方便 。
1 namespace Test
2 {
3 class Program
4 {
5 delegate string MyDelegate(string name);
6
7 static void Main(string[] args)
8 {
9 ThreadMessage("Main Thread");
10
11 //建立委託
12 MyDelegate myDelegate = new MyDelegate(Hello);
13
14 //非同步呼叫委託,獲取計算結果
15 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
16
17 while (!result.AsyncWaitHandle.WaitOne(200))
18 {
19 Console.WriteLine("Main thead do work!");
20 }
21 string data=myDelegate.EndInvoke(result);
22 Console.WriteLine(data);
23
24 Console.ReadKey();
25 }
26
27 static string Hello(string name)
28 {
29 ThreadMessage("Async Thread");
30 Thread.Sleep(2000);
31 return "Hello " + name;
32 }
33
34 static void ThreadMessage(string data)
35 {
36 string message = string.Format("{0}\n ThreadId is:{1}",
37 data,Thread.CurrentThread.ManagedThreadId);
38 Console.WriteLine(message);
39 }
40 }
當要監視多個執行物件的時候,使用IAsyncResult.WaitHandle.WaitOne可就派不上用場了。
幸好.NET為WaitHandle準備了另外兩個靜態方法:WaitAny(waitHandle[], int)與WaitAll (waitHandle[] , int)。
其中WaitAll在等待所有waitHandle完成後再返回一個bool值。
而WaitAny是等待其中一個waitHandle完成後就返回一個int,這個int是代表已完成waitHandle在waitHandle[]中的陣列索引。
下面就是使用WaitAll的例子,執行結果與使用 IAsyncResult.IsCompleted 相同。
1 class Program
2 {
3 delegate string MyDelegate(string name);
4
5 static void Main(string[] args)
6 {
7 ThreadMessage("Main Thread");
8
9 //建立委託
10 MyDelegate myDelegate = new MyDelegate(Hello);
11
12 //非同步呼叫委託,獲取計算結果
13 IAsyncResult result=myDelegate.BeginInvoke("Leslie", null, null);
14
15 //此處可加入多個檢測物件
16 WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle,........ };
17 while (!WaitHandle.WaitAll(waitHandleList,200))
18 {
19 Console.WriteLine("Main thead do work!");
20 }
21 string data=myDelegate.EndInvoke(result);
22 Console.WriteLine(data);
23
24 Console.ReadKey();
25 }
26
27 static string Hello(string name)
28 {
29 ThreadMessage("Async Thread");
30 Thread.Sleep(2000);
31 return "Hello " + name;
32 }
33
34 static void ThreadMessage(string data)
35 {
36 string message = string.Format("{0}\n ThreadId is:{1}",
37 data,Thread.CurrentThread.ManagedThreadId);
38 Console.WriteLine(message);
39 }
40 }
4.7 回撥函式
使用輪詢方式來檢測非同步方法的狀態非常麻煩,而且效率不高,有見及此,.NET為 IAsyncResult BeginInvoke(AsyncCallback , object)準備了一個回撥函式。使用 AsyncCallback 就可以繫結一個方法作為回撥函式,回撥函式必須是帶引數 IAsyncResult 且無返回值的方法: void AsycnCallbackMethod(IAsyncResult result) 。在BeginInvoke方法完成後,系統就會呼叫AsyncCallback所繫結的回撥函式,最後回撥函式中呼叫 XXX EndInvoke(IAsyncResult result) 就可以結束非同步方法,它的返回值型別與委託的返回值一致。
1 class Program
2 {
3 delegate string MyDelegate(string name);
4
5 static void Main(string[] args)
6 {
7 ThreadMessage("Main Thread");
8
9 //建立委託
10 MyDelegate myDelegate = new MyDelegate(Hello);
11 //非同步呼叫委託,獲取計算結果
12 myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), null);
13 //在啟動非同步執行緒後,主執行緒可以繼續工作而不需要等待
14 for (int n = 0; n < 6; n++)
15 Console.WriteLine(" Main thread do work!");
16 Console.WriteLine("");
17
18 Console.ReadKey();
19 }
20
21 static string Hello(string name)
22 {
23 ThreadMessage("Async Thread");
24 Thread.Sleep(2000); \\模擬非同步操作
25 return "\nHello " + name;
26 }
27
28 static void Completed(IAsyncResult result)
29 {
30 ThreadMessage("Async Completed");
31
32 //獲取委託物件,呼叫EndInvoke方法獲取執行結果
33 AsyncResult _result = (AsyncResult)result;
34 MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate;
35 string data = myDelegate.EndInvoke(_result);
36 Console.WriteLine(data);
37 }
38
39 static void ThreadMessage(string data)
40 {
41 string message = string.Format("{0}\n ThreadId is:{1}",
42 data, Thread.CurrentThread.ManagedThreadId);
43 Console.WriteLine(message);
44 }
45 }
可以看到,主線在呼叫BeginInvoke方法可以繼續執行其他命令,而無需再等待了,這無疑比使用輪詢方式判斷非同步方法是否完成更有優勢。
在非同步方法執行完成後將會呼叫AsyncCallback所繫結的回撥函式,注意一點,回撥函式依然是在非同步執行緒中執行,這樣就不會影響主執行緒的執行,這也使用回撥函式最值得青昧的地方。
在回撥函式中有一個既定的引數IAsyncResult,把IAsyncResult強制轉換為AsyncResult後,就可以通過 AsyncResult.AsyncDelegate 獲取原委託,再使用EndInvoke方法獲取計算結果。
執行結果如下:
如果想為回撥函式傳送一些外部資訊,就可以利用BeginInvoke(AsyncCallback,object)的最後一個引數object,它允許外部向回撥函式輸入任何型別的引數。只需要在回撥函式中利用 AsyncResult.AsyncState 就可以獲取object物件。
1 class Program
2 {
3 public class Person
4 {
5 public string Name;
6 public int Age;
7 }
8
9 delegate string MyDelegate(string name);
10
11 static void Main(string[] args)
12 {
13 ThreadMessage("Main Thread");
14
15 //建立委託
16 MyDelegate myDelegate = new MyDelegate(Hello);
17
18 //建立Person物件
19 Person person = new Person();
20 person.Name = "Elva";
21 person.Age = 27;
22
23 //非同步呼叫委託,輸入引數物件person, 獲取計算結果
24 myDelegate.BeginInvoke("Leslie", new AsyncCallback(Completed), person);
25
26 //在啟動非同步執行緒後,主執行緒可以繼續工作而不需要等待
27 for (int n = 0; n < 6; n++)
28 Console.WriteLine(" Main thread do work!");
29 Console.WriteLine("");
30
31 Console.ReadKey();
32 }
33
34 static string Hello(string name)
35 {
36 ThreadMessage("Async Thread");
37 Thread.Sleep(2000);
38 return "\nHello " + name;
39 }
40
41 static void Completed(IAsyncResult result)
42 {
43 ThreadMessage("Async Completed");
44
45 //獲取委託物件,呼叫EndInvoke方法獲取執行結果
46 AsyncResult _result = (AsyncResult)result;
47 MyDelegate myDelegate = (MyDelegate)_result.AsyncDelegate;
48 string data = myDelegate.EndInvoke(_result);
49 //獲取Person物件
50 Person person = (Person)result.AsyncState;
51 string message = person.Name + "'s age is " + person.Age.ToString();
52
53 Console.WriteLine(data+"\n"+message);
54 }
55
56 static void ThreadMessage(string data)
57 {
58 string message = string.Format("{0}\n ThreadId is:{1}",
59 data, Thread.CurrentThread.ManagedThreadId);
60 Console.WriteLine(message);
61 }
62 }
執行結果:
轉載於:http://www.cnblogs.com/leslies2/archive/2012/02/07/2310495.html