20181106_執行緒之異常_取消_變數_安全Lock
阿新 • • 發佈:2018-11-06
一. 執行緒的異常處理:
try { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); //執行緒裡面的異常是被吞掉了,因為已經脫離try catch的範圍了 如果真想讓執行緒之外的Catch抓到異常, 那麼只有使用 WaitAll 抓到多執行緒裡面全部的異常 //實際開發: 執行緒裡面的action不允許出現異常,自己使用try catch處理好 for (int i = 0; i < 20; i++) { string name = string.Format($"btnThreadCore_Click_{i}"); Action<object> act = t => { try { Thread.Sleep(2000); if (t.ToString().Equals("btnThreadCore_Click_11")) { throw new Exception(string.Format($"{t} 執行失敗")); } if (t.ToString().Equals("btnThreadCore_Click_12")) { throw new Exception(string.Format($"{t} 執行失敗")); } Console.WriteLine("{0} 執行成功", t); } catch (Exception ex) { Console.WriteLine($"Exception:{ex.Message}"); // } }; taskList.Add(taskFactory.StartNew(act, name)); } // Task.WaitAll(taskList.ToArray());使用 WaitAll 捕獲多執行緒裡面全部的異常; 但是也不能總是WaitAll, 並且很多業務也不能使用WaitAll來處理 } catch (AggregateException aex) //專門處理多執行緒的異常, 裡面的異常有多項 { foreach (var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); }
二. 執行緒的取消,
a) 場景: 多個執行緒併發,某個失敗後,希望通知別的執行緒,都停下來, 儘量使用CancellationTokenSource, 不要自己建立bool變數
try { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); //多個執行緒併發,某個失敗後,希望通知別的執行緒,都停下來 //task是外部無法中止(中間停止),Thread.Abort不靠譜,因為執行緒是OS的資源,無法掌控啥時候取消 //執行緒的停止: 執行緒自己停止自己--公共的訪問變數--修改它---執行緒不斷的檢測它(延遲少不了) //CancellationTokenSource去標誌任務是否取消 Cancel取消 IsCancellationRequested 是否已經取消了 //Token 啟動Task的時候傳入,那麼如果Cancel了,這個任務會放棄啟動,丟擲一個異常 // 當一個執行緒不執行, 後面的執行緒全部都不能再執行了 CancellationTokenSource cts = new CancellationTokenSource();//bool值 //bool flag = true; 執行緒安全的 for (int i = 0; i < 40; i++) { string name = string.Format("btnThreadCore_Click{0}", i); Action<object> act = t => { try { //if (cts.IsCancellationRequested) //{ // Console.WriteLine("{0} 取消一個任務的執行", t); //} Thread.Sleep(2000); if (t.ToString().Equals("btnThreadCore_Click11")) { throw new Exception(string.Format("{0} 執行失敗", t)); } if (t.ToString().Equals("btnThreadCore_Click12")) { throw new Exception(string.Format("{0} 執行失敗", t)); } if (cts.IsCancellationRequested)//啟動40個執行緒, 每一個執行緒都檢查這個訊號量, 是否改任務已經被取消 { Console.WriteLine("{0} 放棄執行", t); return; } else { Console.WriteLine("{0} 執行成功", t); } } catch (Exception ex) { cts.Cancel(); //當執行丟擲異常後, (t.ToString().Equals("btnThreadCore_Click11")); 將訊號量設定為false, 表示取消任務 Console.WriteLine(ex.Message); } }; taskList.Add(taskFactory.StartNew(act, name, cts.Token)); //如果一個執行緒在啟動的時候標識了cts.Token, 當cts.Cancel()時, 如果這個執行緒還沒有啟動, 則會被標識為取消一個任務執行; 如果這個執行緒已經啟動, 但是還沒有執行, 那麼會被放棄執行 } Task.WaitAll(taskList.ToArray()); } catch (AggregateException aex) //專門處理多執行緒的異常 { foreach (var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); }
三. 多執行緒臨時變數
for (int i = 0; i < 5; i++) { Task.Run(() => { Thread.Sleep(100); Console.WriteLine(i); //列印結果 5個5 }); } for (int i = 0; i < 5; i++) { int k = i; Task.Run(() => { Thread.Sleep(100); Console.WriteLine(k); //列印結果 4,3,1,2,0 }); } //i最後是5 全程就只有一個i 等著列印的時候,i==5 //k 全程有5個k 分別是0 1 2 3 4 //k在外面宣告 全程就只有一個k 等著列印的時候,k==4 int k = 0; for (int i = 0; i < 5; i++) { // int k = i; k = i; new Action(() => { Thread.Sleep(100); Console.WriteLine($"k={k} i={i}"); }).BeginInvoke(null, null); }
四. 執行緒安全:
try { TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); //如何判斷何時加鎖: 執行緒內部宣告的變數, 由於不共享的是執行緒安全的; 但是線上程外部操作的資源由於共享, 則就會造成執行緒不安全. 比如全域性變數/資料庫的某個值/磁碟檔案 int TotalCountIn = 0;//TotalCountIn屬於執行緒外部變數, 會有執行緒安全有問題 for (int i = 0; i < 10000; i++)//i屬於執行緒外部變數, 會有執行緒安全有問題 { int newI = i;//newI 屬於執行緒內部變數, 不會有執行緒安全有問題 taskList.Add(taskFactory.StartNew(() => { //值型別不能lock lock (btnThreadCore_Click_Lock)//lock後的方法塊,任意時刻只有一個執行緒可以進入 //只能鎖引用型別,佔用這個引用連結 不要用string 因為享元 , 可能導致鎖的是同一塊記憶體區域 { //這裡就是單執行緒 this.TotalCount += 1; TotalCountIn += 1; this.IntList.Add(newI); } })); } Task.WaitAll(taskList.ToArray()); Console.WriteLine(this.TotalCount); Console.WriteLine(TotalCountIn); Console.WriteLine(this.IntList.Count()); #endregion } catch (AggregateException aex) //專門處理多執行緒的異常 { foreach (var item in aex.InnerExceptions) { Console.WriteLine(item.Message); } } catch (Exception ex) { Console.WriteLine(ex.Message); }
五. 關於Lock變數的寫法解釋:
//為什麼鎖的變數要這樣寫private static readonly object btnThreadCore_Click_Lock = new object(); //private 防止外面也去鎖 //static 全場唯一 //readonly不準修改, 如果不是readonly有可能會在鎖的程式碼裡面修改它 //object表示引用, 因為lock不能鎖值型別
六. 為什麼不推薦鎖this
//lock (this) //{ // //this form1的例項 每次例項化是不同的鎖,同一個例項是相同的鎖 // //但是這個例項別人也能訪問到,別人也能鎖定 // //最好不要鎖this //}
七. Lock到底是個什麼?
//Monitor.Enter(btnThreadCore_Click_Lock); //lock 就是一個語法糖, 類似於monitor的寫法, 也就是說可以將lock{ }塊中的語句, 放到Monitor的Enter和exit中間 //Monitor.Exit(btnThreadCore_Click_Lock); Monitor.Enter(btnThreadCore_Click_Lock); //lock 就是一個語法糖, 類似於monitor的寫法, 也就是說可以將lock{ }塊中的語句, 放到Monitor的Enter和exit中間 { //這裡就是單執行緒 this.TotalCount += 1; TotalCountIn += 1; this.IntList.Add(newI); } Monitor.Exit(btnThreadCore_Click_Lock);
八. 不使用lock解決,執行緒安全問題:
//使用lock 解決的時候,因為只有一個執行緒可以操作資料, 沒有併發, 所以解決了問題 但是犧牲了效能,所以要儘量縮小lock的範圍 //解決辦法:1. 開發中最好不要有衝突, 能拆分就優先拆分, 比如在1億條資料中進行操作, 那麼可以先將資料拆分成不同的小塊, 然後再合併成1億條資料 //2. 不使用locak, 可以使用安全佇列 System.Collections.Concurrent.ConcurrentQueue來解決, 一個執行緒去完成操作