<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們在單執行緒中,捕獲異常可以使用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集合 List<Task> taskList = new List<Task>(); 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集合 List<Task> taskList = new List<Task>(); 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型別的集合 List<Task> taskList = new List<Task>(); 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,我們還可以使用資料分拆,避免多執行緒操作同一個資料,這樣又安全又高效。
到此這篇關於C#多執行緒相關操作的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45