.Net多執行緒程式設計—誤用點分析
阿新 • • 發佈:2022-05-03
1 共享變數問題
錯誤寫法:
所有的任務可能會共享同一個變數,所以輸出結果可能會一樣。
1 public static void Error()
2 {
3 for(int i=0;i<10;i++)
4 {
5 Task.Run(() => { Console.WriteLine("{0}", i); });
6 }
7 }
正確寫法:
將變數i賦給區域性變數temp,使得每一個任務使用不同的i值。
1 public static void Right() 2 { 3 for (int i = 0; i < 10; i++) 4 { 5 int temp = i; 6 Task.Run(() => { Console.WriteLine("{0}", temp); }); 7 } 8 }
2 不要清理掛起任務所需資源
錯誤寫法:
非同步輸出文字內容,所以在未使用完StreamReader的時候,變數sr已經離開它的作用域,呼叫Dispose方法。
1 public static void Error() 2 { 3 using (StreamReader sr = new StreamReader(@"D:說明.txt", Encoding.Default)) 4 { 5 Task.Run(() => { Console.WriteLine("輸出:{0}",sr.ReadLine()); }); 6 } 7 }
正確寫法:
1 public static void Right()
2 {
3 using (StreamReader sr = new StreamReader(@"D:說明.txt", Encoding.Default))
4 {
5 var task = Task.Run(() => { Console.WriteLine("輸出:{0}", sr.ReadLine()); });
6 task.Wait();
7 }
8 }
3避免鎖定this,typeof(type),string
正確的做法:定義一個object型別的私有隻讀欄位,鎖定之。
4 關於WaitHandle.WaitAll的waitHandles的數目必須小於等於64個
1 public static void Error()
2 {
3 ManualResetEvent[] manualEvents = new ManualResetEvent[65];
4
5 try
6 {
7 for (int i = 0; i < 65; i++)
8 {
9 var temp = i;
10 Task.Run(() =>
11 {
12 manualEvents[temp] = new ManualResetEvent(false);
13 Console.WriteLine("{0}", temp);
14 manualEvents[temp].Set();
15 });
16 }
17 WaitHandle.WaitAll(manualEvents);
18 }
19 catch (Exception ae)
20 {
21 Console.WriteLine(ae.Message);
22 }
23 }
5 無法捕獲異常的情形
1 try
2 {
3 var task = Task.Run(() => { throw new Exception("拋異常"); });
4 //如果將下面這行程式碼注掉,則無法丟擲異常
5 task.Wait();
6 }
7 catch(Exception ex)
8 {
9 Console.WriteLine(ex.Message);
10 }
6 是否該釋放Task資源
建議呼叫Dispose,但不呼叫也不是一個嚴重的錯誤。
注意在Task任務處於某些狀態時是不允許釋放資源的,否則會報錯。
1 public static void CatchException()
2 {
3 try
4 {
5 Console.WriteLine("開始");
6 var task = Task.Run(() =>
7 {
8 //throw new Exception("拋異常");
9 });
10 //注掉下面這行程式碼,觀察異常結果
11 //task.Wait();
12 task.Dispose();
13 Console.WriteLine("結束");
14
15 }
16 catch(Exception ex)
17 {
18 Console.WriteLine(ex.Message);
19 }
20 }
7死鎖演示
假設tsak1和task2都在獲得第二個鎖(對tsak1來說它請求的第二個鎖是LockedObj2 ,而對task2來說則是LockedObj1 )之前成功獲得了第一個鎖,就會發生死鎖。
1 private static readonly Object LockedObj1 = new object();
2 private static readonly Object LockedObj2 = new object();
3 public static void LockShow()
4 {
5 var task1 = Task.Run(() =>
6 {
7 lock (LockedObj1)
8 {
9 Console.WriteLine("get LockedObj1");
10 lock (LockedObj2)
11 {
12 Console.WriteLine("get LockedObj2....");
13 }
14 }
15 });
16
17 var task2 = Task.Run(() =>
18 {
19 lock (LockedObj2)
20 {
21 Console.WriteLine("get LockedObj2");
22 lock (LockedObj1)
23 {
24 Console.WriteLine("get LockedObj1....");
25 }
26 }
27 });
28 }
多次執行可得下面兩種結果:第一個圖是未發生死鎖的情形,第二個圖是發生死鎖的情形。
8 不要呼叫Thread.Abort方法。
Task沒有提供Abort方法,使用新的TPL(.NET 4.0以後),不會想到這個問題,一般使用CancellationToken來控制取消任務。
9 確保共享變數是安全的
反覆執行,可觀察到不一樣的結果,下圖所示。
1 public static void Func()
2 {
3 string s = "ASDFGH";
4 Parallel.Invoke(
5 () => { s = s.Replace("A", "1"); s = s.Replace("S", "1s"); },
6 () => { s = s.Replace("A", "2"); s = s.Replace("S", "2s"); },
7 () => { s = s.Replace("A", "3"); });
8
9 Console.WriteLine(s);
10 }
10 處理器超額申請與申請不足
1 public static void Func()
2 {
3 ParallelOptions po = new ParallelOptions();
4 //超額申請,處理器只有4個邏輯核心,結果設定並行度為10且是個邏輯核心均在工作,等待的任務數量大於0.
5 po.MaxDegreeOfParallelism = 10;
6
7 //申請不足,處理器有4個邏輯核心,卻指定並行度為3,還有一個空閒的核心沒有被佔用(也有可能被其他執行緒佔用,這裡假設在指定並行度為3的情況下,另一個核心空閒)
8 po.MaxDegreeOfParallelism = 3;
9
10 List<int> list = new List<int>();
11 Parallel.ForEach(list, po, m =>
12 {
13 //業務
14 });
15 }
-----------------------------------------------------------------------------------------
時間倉促,水平有限,如有不當之處,歡迎指正。