1. 程式人生 > >記一次Task拋異常,呼叫執行緒處理而引發的一些隨想

記一次Task拋異常,呼叫執行緒處理而引發的一些隨想

記一次Task拋異常,呼叫執行緒處理而引發的一些隨想

多執行緒呼叫,任務執行緒丟擲異常如何在另一個執行緒(呼叫執行緒)中捕獲並進行處理的問題。

1.任務執行緒在任務執行緒執行語句上丟擲異常。

例如:

 1   private void button2_Click(object sender, EventArgs e)
 2         {
 3             try
 4             {
 5                 var task = Task.Factory.StartNew<bool>(() =>
 6                 {
 7                     //Do Some Things 
 8                     throw new Exception("Task Throw Exception!");
 9                     //return true;
10                 });
11 
12                 //var result = task.Wait(20000);
13                 var result = task.Result;
14             }
15             catch (Exception ex)
16             {
17                 
18             }
19 
20         }

除錯結果:在Task.Rrsult或者Wait時可以丟擲任務異常,並在呼叫執行緒中通過try-catch捕獲處理。

 

 2.任務執行緒在非同步委託執行語句上丟擲異常。

 1      private void button3_Click(object sender, EventArgs e)
 2         {
 3             var fun = new Func<int>(() =>
 4               {
 5                   //do sonmething
 6                   throw new Exception("Task Throw Exception!");
 7                   return 1;
 8               });
 9             try
10             {
11                 var task = Task.Factory.StartNew<bool>(() =>
12                 {
13                     try
14                     {
15                         var res = fun.BeginInvoke(null, null);
16                         //do some thing
17                         var ob = fun.EndInvoke(res);
18                     }
19                     catch (Exception ex)
20                     {
21 
22                         throw ex;
23                     }
24                     return true;
25                 });
26                 var result = task.Wait(20000);
27                 //var result1 = task.Result;
28             }
29             catch (Exception ex)
30             {
31 
32             }
33         }

除錯可知:非同步委託在呼叫EndInvoke(res)獲取結果時可以捕獲委託內部異常並丟擲由外部Task抓取。

 

 2.任務執行緒在視窗控制代碼(建立控制元件)執行緒上拋異常現象。

control.invoke(引數delegate)方法:在擁有此控制元件的基礎視窗控制代碼的執行緒上執行指定的委託。

control.begininvoke(引數delegate)方法:在建立控制元件的基礎控制代碼所線上程上非同步執行指定委託。

即invoke表是同步、begininvoke表示非同步。但是如何來進行同步和非同步呢?

 2.1Invoke方法執行規則

 Invoke的原理是藉助訊息迴圈通知主執行緒,並且在主執行緒執行委託。直接程式碼檢視:

 1  private void button1_Click(object sender, EventArgs e)
 2         {
 3             //Invoke的原理是藉助訊息迴圈通知主執行緒,並且在主執行緒執行委託。
 4             try
 5             {
 6                 var thIdMain = Thread.CurrentThread.ManagedThreadId;
 7                 Console.WriteLine($"Load start: Main Thread ID:{thIdMain}");
 8                 var task = Task.Factory.StartNew<bool>(() =>
 9                 {
10                     var taskId = Thread.CurrentThread.ManagedThreadId;
11                     Console.WriteLine($"Task start: Task Thread ID:{taskId}");
12                     var res = this.Invoke(new Func<int>(() =>
13                     {
14                         var InvokeId = Thread.CurrentThread.ManagedThreadId;
15                         Console.WriteLine($"Invoke in: Begion Invoke Thread ID:{InvokeId}");
16                         //do sonmething
17                         return 1;
18                     }));
19                     taskId = Thread.CurrentThread.ManagedThreadId;
20                     Console.WriteLine($"Invoke out ,Thread ID:{taskId}");
21                     return true;
22 
23                 });
24                
25                 thIdMain = Thread.CurrentThread.ManagedThreadId;
26                 Console.WriteLine($"Wait: Main Thread ID:{thIdMain}");
27                 var CanLoad = task.Wait(2000);//.Result;
28                 thIdMain = Thread.CurrentThread.ManagedThreadId;
29                 Console.WriteLine($"End: Main Thread ID:{thIdMain}");
30             }
31             catch (Exception) { }
32         }

執行輸出:

Load start: Main Thread ID:1
Wait: Main Thread ID:1
Task start: Task Thread ID:3
End: Main Thread ID:1
Invoke in: Begion Invoke Thread ID:1
Invoke out ,Thread ID:3

檢視輸出順序說明:invoke在主執行緒中執行,但是,invoke後面的程式碼必須在Invoke委託方法執行完成後,才能繼續執行;而invoke在主執行緒中執行,所以其執行時機無法確定,得等訊息迴圈(主執行緒)中其它訊息執行後才能進行。

 2.2BeginInvoke方法執行規則

 BeginInvoke也在主執行緒執行相應委託。直接程式碼檢視:

 1       private void button1_Click(object sender, EventArgs e)
 2         {
 3             //BeginInvoke
 4             try
 5             {
 6                 var thIdMain = Thread.CurrentThread.ManagedThreadId;
 7                 Console.WriteLine($"Load start: Main Thread ID:{thIdMain}");
 8                 var task = Task.Factory.StartNew<bool>(() =>
 9                 {
10                     var taskId = Thread.CurrentThread.ManagedThreadId;
11                     Console.WriteLine($"Task start: Task Thread ID:{taskId}");
12                     var res = this.BeginInvoke(new Func<int>(() =>
13                     {
14                         var BegionInvokeId = Thread.CurrentThread.ManagedThreadId;
15                         Console.WriteLine($"BeginInvoke in: Begion Invoke Thread ID:{BegionInvokeId}");
16                         //do sonmething
17                         return 1;
18                     }));
19                     taskId = Thread.CurrentThread.ManagedThreadId;
20                     Console.WriteLine($"BeginInvoke is Completed: {res.IsCompleted}, Thread ID:{taskId}");
21                     var ob = this.EndInvoke(res);
22                     taskId = Thread.CurrentThread.ManagedThreadId;
23                     Console.WriteLine($"BeginInvoke out ,Thread ID:{taskId}");
24                     // Console.WriteLine(ob.ToString());
25                     return true;
26                 });
27                 long i = 0;
28                 while (i < 1000000000)//延時
29                 {
30                     i++;
31                 }
32                 thIdMain = Thread.CurrentThread.ManagedThreadId;
33                 Console.WriteLine($"Wait: Main Thread ID:{thIdMain}");
34                 //var CanLoad = task.Wait(2000);//.Result;
35                 thIdMain = Thread.CurrentThread.ManagedThreadId;
36                 Console.WriteLine($"End: Main Thread ID:{thIdMain}");
37                 //Console.WriteLine(CanLoad);
38             }
39             catch (Exception) { }
40         }

執行輸出:

Load start: Main Thread ID:1
Task start: Task Thread ID:3
BeginInvoke is Completed: False, Thread ID:3
Wait: Main Thread ID:1
End: Main Thread ID:1
BeginInvoke in: Begion Invoke Thread ID:1
BeginInvoke out ,Thread ID:3

根據輸出結果可知begininvoke所提交的委託方法也是在主執行緒中執行,BeginInvoke is Completed: False, Thread ID:3與Wait: Main Thread ID:1兩段比較,會發現begininvoke提交委託方法後,子執行緒繼續執行,不需要等待委託方法的完成。

總結:invoke和begininvoke都是在主執行緒中執行。invoke提交的委託方法執行完成後,才能繼續執行;begininvoke提交委託方法後,子執行緒繼續執行。invoke(同步)和begininvoke(非同步)的含義,是相對於子執行緒而言的,實際上對於控制元件的呼叫總是由主執行緒來執行。

 2.3 Control.BeginInvoke或者Control.Invoke執行委託時丟擲異常

Control.Invoke執行委託時丟擲異常:

 1   private void button2_Click(object sender, EventArgs e)
 2         {
 3             try
 4             {
 5                 var task = Task.Factory.StartNew<bool>(() =>
 6                 {
 7                     try
 8                     {
 9                     //Do Some Things 
10                     var res = this.Invoke(new Func<int>(() =>
11                     {
12                         //do sonmething
13                         throw new Exception("Task Throw Exception!");
14                         return 1;
15                     }));
16                     }
17                     catch (Exception ex)
18                     {
19 
20                         throw ex;
21                     }
22                     return true;
23                 });
24             }
25             catch (Exception ex)
26             {
27                 
28             }
29         }

執行結果:只有task中的try可以捕捉,繼續丟擲,主執行緒捕捉不到 

 

 原因分析:button2_Click方法和task中invoke都是在主執行緒中執行。但是,invoke必須等主執行緒中其它訊息執行完即button2_Click程式碼執行完退出才有機會執行。此時button2_Click方法執行完,所分配的記憶體空間被回收(失效),故即便task繼續拋異常均不能捕獲到。而Invoke在執行完成時,task後續程式碼阻斷並等待其執行完,後續執行程式碼與其在記憶體上屬於同一 堆疊,故可以捕獲到Invoke丟擲的異常。

Control.BeginInvoke執行委託時丟擲異常:

 1    private void button2_Click(object sender, EventArgs e)
 2         {
 3             try
 4             {
 5                 var task = Task.Factory.StartNew<bool>(() =>
 6                 {
 7                     try
 8                     {
 9                     //Do Some Things 
10                     var res = this.BeginInvoke(new Func<int>(() =>
11                     {
12                         //do sonmething
13                         throw new Exception("Task Throw Exception!");
14                         return 1;
15                     }));
16 
17                         var ob = this.EndInvoke(res);
18                     }
19                     catch (Exception ex)
20                     {
21 
22                         throw ex;
23                     }
24                     return true;
25                 });
26             }
27             catch (Exception ex)
28             {
29                 
30             }
31 
32         }

執行結果:均無法捕捉異常。但是Main函式中可以。

  原因分析:button2_Click方法和task中BeginInvoke都是在主執行緒中執行。但是,BeginInvoke須等主執行緒中其它訊息執行完即button2_Click程式碼執行完退出才有機會執行。此時button2_Click方法執行完,所分配的記憶體空間被回收(失效),故即便task繼續跑異常均不能捕獲到。而BeginInvoke在執行完成時,task後續程式碼無須阻斷等待其執行完,二者在記憶體上不屬於同一 堆疊, 而非同步呼叫時,非同步執行期間產生的異常由CRL庫捕獲,你並一般在呼叫EndInvoke函式獲取執行結果時CRL會丟擲引發非同步執行期間產生的異常,但是,CRL對Control.BeginInvoke特殊處理並未丟擲(個人猜想,待驗證)。故此時Task無法捕獲到BeginInvoke丟擲的異常。

 一般BeginInvoke與Invoke主要用於更新控制元件相關屬性值,特意拋異常的可能性應該比較小,如果有異常可以在該委託裡面就進行解決了。此處僅作對技術研究的一個記錄。

&n