記一次Task拋異常,調用線程處理而引發的一些隨想

来源:https://www.cnblogs.com/VueDi/archive/2020/04/08/11527443.html
-Advertisement-
Play Games

多線程調用,任務線程拋出異常如何在另一個線程(調用線程)中捕獲併進行處理的問題。 ...


記一次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主要用於更新控制項相關屬性值,特意拋異常的可能性應該比較小,如果有異常可以在該委托裡面就進行解決了。此處僅作對技術研究的一個記錄。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.深淺拷貝 拷貝模塊 不可變類型(元組除外)拷貝後記憶體地址相同 可變類型,拷貝後會新生成一個記憶體地址 淺拷貝 只拷貝整個數據類型的錶面記憶體地址 無數據類型嵌套 有數據類型嵌套 深拷貝 不管數據類型有幾層都重新創建記憶體地址存儲 特殊性 元組 如果元組只有一層那麼深淺拷貝記憶體地址都不變 如果元組中嵌套 ...
  • JSR303 是 Java EE 6 中的一項子規範,叫做 Bean Validation,官方參考實現是hibernate Validator,有了它,我們可以在實體類的欄位上標註不同的註解實現對數據的校驗,不用 判斷,簡化了我們的開發,而且可讀性也很好。 但有時候它提供的註解並不能滿足我們的要求 ...
  • 相關資料:https://wiki.nesdev.com/w/index.php/Status_flags 根據下麵的測試結果,總結如下: 溢出標誌--將寄存器中的數據當做有符號數看待,當計算結果大於127或小於-128,則溢出 進位標誌--當做無符號數看待。ADC指令,超過255則進位標誌置1。S ...
  • 題目描述 把一個數組最開始的若幹個元素搬到數組的末尾,我們稱之為數組的旋轉。輸入一個非遞減排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。NOTE:給出的所有元素都大於0,若數組大小為0,請返回0。 解題思路 鏈接: ...
  • java的訪問許可權 一、訪問許可權 1. 許可權簡介 java有四種訪問許可權:public > protected > default > private。 註:protected、private不能修飾類,private不能修飾抽象方法。 2. 許可權詳情 public 所修飾的成員,在任何位置均可被訪 ...
  • *.sql文件導入資料庫通常比較方便。 其他格式如csv、xsl等文件導入比較費勁。 推薦一個針對csv\xsl的導入工具:https://github.com/zhengze/file_to_db.git 如果文件第一行是table的欄位名,自動建表+導入數據,實在是省事。 具體用法請看READM ...
  • 在我的工作經驗中,在C#語言本身的學習上花了大量的時間,積累了一些經驗,一些是在學習和工作中遇到的問題和解決辦法分享出來,希望大家也能有收穫。有些表述錯誤的地方,也希望及時指正。 (一)VSExxx.dll的使用 程式的運行以平臺系統位數不匹配,64位系統上C#調用32位的C++ *.dll,其原因 ...
  • 1 前言 之前的幾篇文章介紹了Lambda和Linq的一些支持方法。這一篇我嘗試通過模擬具體的業務場景來描述一下Linq的兩種查詢方式的使用。 一直提的Linq查詢方式分為兩種,一種就是方法鏈的形式,官方的稱呼是流式查詢;另一種是類似於SQL語句的查詢方式,我之前叫做類SQL查詢方式,不過有的文檔稱 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...