網站首頁 編程語言 正文
一、線程異常
我們在單線程中,捕獲異常可以使用try-catch,代碼如下所示:
using System; namespace MultithreadingOption { class Program { static void Main(string[] args) { #region 單線程中捕獲異常 try { int[] array = { 1, 23, 61, 678, 23, 45 }; Console.WriteLine(array[6]); } catch (Exception ex) { Console.WriteLine($"message:{ex.Message}"); } #endregion Console.ReadKey(); } } }
程序運行結果:
那么在多線程中如何捕獲異常呢?是不是也可以使用try-catch進行捕獲?我們先看下面的代碼:
using System; using System.Threading.Tasks; namespace MultithreadingOption { class Program { static void Main(string[] args) { #region 單線程中捕獲異常 //try //{ // int[] array = { 1, 23, 61, 678, 23, 45 }; // Console.WriteLine(array[6]); //} //catch (Exception ex) //{ // Console.WriteLine($"message:{ex.Message}"); //} #endregion #region 多線程中的異常 try { for (int i = 0; i < 30; i++) { string str = $"main_{i}"; // 開啟線程 Task.Run(() => { Console.WriteLine($"{str} 開始了"); if(str.Equals("main_5")) { throw new Exception("main_5 發生了異常"); } else if (str.Equals("main_11")) { throw new Exception("main_11 發生了異常"); } else if (str.Equals("main_18")) { throw new Exception("main_18 發生了異常"); } Console.WriteLine($"{str} 結束了"); }); } } catch (Exception ex) { Console.WriteLine($"message:{ex.Message}"); } #endregion Console.ReadKey(); } } }
程序運行結果:
我們看到結果中并沒有輸出異常信息,是不是沒有拋出異常呢?我們起代碼進行調試,看調試信息:
我們看到程序中確實也拋出了異常,但是程序卻沒有捕獲到,那么異常去哪里了呢?異常被多線程給吞掉了,那么如何在多線程中捕獲異常呢?如果把try-catch寫在線程里面呢?每一個線程都是單線程的,把try-catch寫在每一個線程里面就沒有意義了。在多線程中捕獲異常,需要使用到WaitAll(),看下面的代碼:
try { // 定義一個Task類型的List集合 ListtaskList = new List (); for (int i = 0; i < 30; i++) { string str = $"main_{i}"; // 開啟線程,并把線程添加到集合中 taskList.Add(Task.Run(() => { Console.WriteLine($"{str} 開始了"); if (str.Equals("main_5")) { throw new Exception("main_5 發生了異常"); } else if (str.Equals("main_11")) { throw new Exception("main_11 發生了異常"); } else if (str.Equals("main_18")) { throw new Exception("main_18 發生了異常"); } Console.WriteLine($"{str} 結束了"); })); } // 等待所有線程都執行完 Task.WaitAll(taskList.ToArray()); } catch (Exception ex) { Console.WriteLine($"message:{ex.Message}"); }
我們用代碼進行調試,調試結果:
這時就可以進入到catch里面了,我們監視ex,發現ex是AggregateException類型的異常,我們在進一步優化代碼:
try { // 定義一個Task類型的List集合 ListtaskList = new List (); for (int i = 0; i < 30; i++) { string str = $"main_{i}"; // 開啟線程,并把線程添加到集合中 taskList.Add(Task.Run(() => { Console.WriteLine($"{str} 開始了"); if (str.Equals("main_5")) { throw new Exception("main_5 發生了異常"); } else if (str.Equals("main_11")) { throw new Exception("main_11 發生了異常"); } else if (str.Equals("main_18")) { throw new Exception("main_18 發生了異常"); } Console.WriteLine($"{str} 結束了"); })); } // 等待所有線程都執行完 Task.WaitAll(taskList.ToArray()); } catch(AggregateException are) { foreach (var exception in are.InnerExceptions) { Console.WriteLine(exception.Message); } } catch (Exception ex) { Console.WriteLine($"message:{ex.Message}"); }
最后運行程序:
我們發現這時就可以捕獲到具體的異常信息了。
二、線程取消
在上面的示例中,我們捕獲到了多線程中發生的異常,并且也輸出了異常信息,但是這樣是不友好的。在實際開發中,我們使用多線程并發執行任務,假如其中某一個任務失敗了或者發生了異常,我們希望可以通知其他的線程,都停止下來,那么該如何做呢?這時就需要使用到線程取消。
Task不能外部終止任務,只能自己終止自己。
.Net框架提供了CancellationTokenSource類,該類里面有一個bool類型的屬性:IsCancellationRequested,默認是false,表示是否取消線程。還提供了一個Cancel()方法,該方法可以把IsCancellationRequested的屬性值設置為true,并且不能在設置回去。代碼如下:
// 實例化對象 CancellationTokenSource cts = new CancellationTokenSource(); for (int i = 0; i < 20; i++) { string str = $"main_{i}"; // 開啟線程 Task.Run(() => { try { Console.WriteLine($"{str} 開始了"); // 暫停 Thread.Sleep(new Random().Next(50, 100) * 100); if (str.Equals("main_5")) { throw new Exception("main_5 發生了異常"); } else if (str.Equals("main_11")) { throw new Exception("main_11 發生了異常"); } if (cts.IsCancellationRequested == false) { Console.WriteLine($"{str} 結束了"); } else { Console.WriteLine($"{str} 線程取消"); } } catch (Exception ex) { // 發生了異常,將IsCancellationRequested的值設置為true cts.Cancel(); Console.WriteLine($"message:{ex.Message}"); } }); }
程序運行結果:
可以看到,當有異常發生之后,有的線程就被取消了。這樣就初步實現了線程取消。
在上面的示例中,我們是先開啟了線程,如果發生了異常,則取消線程。那么會有這樣一種情況:線程中發生了異常,可能這時候有的線程還沒有開啟,那么能不能就不讓這些線程在開啟呢?Task的Run方法有一個重載:
第二個參數就表示取消線程。而且CancellationTokenSource類里面正好有這個參數:
所以,我們可以利用Run方法的重載來實現不開啟線程,代碼如下:
try { // 實例化對象 CancellationTokenSource cts = new CancellationTokenSource(); // 創建Task類型的集合 ListtaskList = new List (); for (int i = 0; i < 20; i++) { string str = $"main_{i}"; // 開啟線程 Task.run 以后 添加Token 就可以在某一個線程發生異常之后,讓沒有開啟的線程不開啟了 taskList.Add(Task.Run(() => { try { Console.WriteLine($"{str} 開始了"); // 暫停 Thread.Sleep(new Random().Next(50, 100) * 10); if (str.Equals("main_5")) { throw new Exception("main_5 發生了異常"); } else if (str.Equals("main_11")) { throw new Exception("main_11 發生了異常"); } if (cts.IsCancellationRequested == false) { Console.WriteLine($"{str} 結束了"); } else { Console.WriteLine($"{str} 線程取消"); } } catch (Exception ex) { // 發生了異常,將IsCancellationRequested的值設置為true cts.Cancel(); } }, cts.Token)); } // 等待所有線程執行完 Task.WaitAll(taskList.ToArray()); } catch (AggregateException are) { foreach (var exception in are.InnerExceptions) { Console.WriteLine(exception.Message); } }
程序運行結果:
輸出結果中有一句話:已取消一個任務,但是我們的代碼里面沒有打印這句話,這是從哪里來的呢?這是因為第二個參數Token的原因,加了這個參數以后,如果就線程發生了異常,就不在繼續開啟線程。
三、臨時變量
我們先來看看下面一段代碼:
for (int i = 0; i < 20; i++) { // 開啟線程 Task.Run(() => { Task.Run(() => Console.WriteLine($"this is {i} ThreadId: {Thread.CurrentThread.ManagedThreadId.ToString("00")}")); }); }
這段代碼的輸出結果是什么呢?我們運行程序查看結果:
可能有人會感到疑惑:為什么輸出的都是20呢,而不是每次循環變量的值?這是什么原因呢。這是因為我們申請線程的時候不會發生阻塞,而且還是延遲執行的。我們知道,代碼的執行速度是非常快的,循環20次幾乎一瞬間就完成了,這是i就變成了20,但是線程是延遲執行的,當線程真正去執行的時候,對應的是同一個i,這時i是20,所以輸出的都是20。那么該如何輸出每次循環的值呢?看下面的代碼:
for (int i = 0; i < 20; i++) { // 定義一個新的變量 int k = i; // 開啟線程 Task.Run(() => { Task.Run(() => Console.WriteLine($"this is {i}_{k} ThreadId: {Thread.CurrentThread.ManagedThreadId.ToString("00")}")); }); }
程序運行結果:
這樣每次循環的時候,都重新定義變量k,保證每次都是全新的,所以k的值就是每次循環的值。
四、線程安全
什么是線程安全呢?線程安全:如果你的代碼在進程中有多個線程同時運行這一段,如果每次運行的結果都跟單線程運行時的結果一致,那么就是線程安全的。
在什么情況下會出現線程安全的問題呢?
一般都是有全局變量/共享變量/靜態變量/硬盤文件/數據庫的值,只要多線程訪問和修改,就會出現線程安全的問題。看下面的代碼:
int syncNum = 0; int AsyncNum = 0; for (int i = 0; i < 10000; i++) { syncNum++; } Console.WriteLine($"syncNum={syncNum}"); //單線程10000 10000 for (int i = 0; i < 10000; i++) { Task.Run(() => { AsyncNum++; }); } Console.WriteLine($"AsyncNum ={AsyncNum}");
程序運行結果:
這就是線程安全造成的問題。那么該如何解決這個問題呢?這時可以使用lock關鍵字解決。lock關鍵字定義如下:
private static readonly object Form_Lock = new object();//鎖對象的標準寫法
修改代碼如下:
int syncNum = 0; int AsyncNum = 0; for (int i = 0; i < 10000; i++) { syncNum++; } Console.WriteLine($"syncNum={syncNum}"); for (int i = 0; i < 10000; i++) { Task.Run(() => { lock (Form_Lock) { AsyncNum++; } }); } // 休眠5秒,等待所有線程都執行完畢 Thread.Sleep(5000); Console.WriteLine($"AsyncNum ={AsyncNum}");
程序運行結果:
除了使用lock,我們還可以使用數據分拆,避免多線程操作同一個數據,這樣又安全又高效。
原文鏈接:https://www.cnblogs.com/dotnet261010/p/12300417.html
相關推薦
- 2022-09-12 shell腳本5種執行方式及腳本不同的執行方法和區別詳解_linux shell
- 2022-07-14 C++實現一個簡單的線程池的示例代碼_C 語言
- 2022-12-06 c++入門必學算法之快速冪思想及實現_C 語言
- 2022-09-06 python使用seaborn繪圖直方圖displot,密度圖,散點圖_python
- 2022-04-01 Kubernetes命令行工具--kubectl管理
- 2022-07-25 C#爬蟲基礎之HttpClient獲取HTTP請求與響應_C#教程
- 2022-05-27 Entity?Framework?Core相關包的概念介紹與安裝_實用技巧
- 2023-04-26 C++變量初始化形式及其默認初始值問題_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支