C# 多線程總結 異常處理 線程取消 鎖(lock)

那麼何時能用多線程? 任務能併發的時候c++

多線程能幹嗎?提高速度/優化用戶體驗數據庫

網站首頁:A數據庫 B接口 C分佈式服務 D搜索引擎,適合多線程併發,都完成後才能返回給用戶,須要等待WaitAll
列表頁:核心數據可能來自數據庫/接口服務/分佈式搜索引擎/緩存,多線程併發請求,哪一個先完成就用哪一個結果,其餘的就無論了api

現實實例緩存

多人合做開發---多線程--提高效率/性能安全

 1               {
 2                 TaskFactory taskFactory = new TaskFactory();
 3                 List<Task> taskList = new List<Task>();
 4                 taskList.Add(taskFactory.StartNew(o=> Coding("A", " Portal"), "A"));
 5                 taskList.Add(taskFactory.StartNew(o=> Coding("B", "    DBA"), "B"));
 6                 taskList.Add(taskFactory.StartNew(o=> Coding("C", " Client"), "C"));
 7                 taskList.Add(taskFactory.StartNew(o=> Coding("D", "Service"), "D"));
 8                 taskList.Add(taskFactory.StartNew(o=> Coding("E", " Wechat"), "E"));
 9 
10                 //誰第一個完成,獲取一個紅包獎勵
11                 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}開發完成,獲取個紅包獎勵{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
12                 //實戰做業完成後,一塊兒慶祝一下
13                 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"開發都完成,一塊兒慶祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));
14                 //ContinueWhenAny  ContinueWhenAll 非阻塞式的回調;並且使用的線程多是新線程,也多是剛完成任務的線程,惟一不多是主線程
15 
16 
17                 //阻塞當前線程,等着任意一個任務完成
18                 Task.WaitAny(taskList.ToArray());//也能夠限時等待
19                 Console.WriteLine("準備環境開始部署");
20                 //須要可以等待所有線程完成任務再繼續  阻塞當前線程,等着所有任務完成
21                 Task.WaitAll(taskList.ToArray());
22                 Console.WriteLine("5個模塊所有完成後,集中調試");
23 
24                 //Task.WaitAny  WaitAll都是阻塞當前線程,等任務完成後執行操做
25                 //阻塞卡界面,是爲了併發以及順序控制
26             }
View Code
 1         /// <summary>
 2         /// 模擬Coding過程
 3         /// </summary>
 4         /// <param name="name"></param>
 5         /// <param name="projectName"></param>
 6         private static string Coding(string name, string projectName)
 7         {
 8             Console.WriteLine($"****************Coding Start  {name} {projectName}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
 9             long lResult = 0;
10             for (int i = 0; i < 1_000_000_000; i++)
11             {
12                 lResult += i;
13             }
14             Console.WriteLine($"****************Coding   End  {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
15             return name;
16         }
View Code

多線程異常處理多線程

 1             #region 多線程異常處理
 2             {
 3                 try
 4                 {
 5 
 6                     List<Task> taskList = new List<Task>();
 7                     for (int i = 0; i < 100; i++)
 8                     {
 9                         string name = $"btnThreadCore_Click_{i}";
10                         taskList.Add(Task.Run(() =>
11                         {
12                             if (name.Equals("btnThreadCore_Click_11"))
13                             {
14                                 throw new Exception("btnThreadCore_Click_11異常");
15                             }
16                             else if (name.Equals("btnThreadCore_Click_12"))
17                             {
18                                 throw new Exception("btnThreadCore_Click_12異常");
19                             }
20                             else if (name.Equals("btnThreadCore_Click_38"))
21                             {
22                                 throw new Exception("btnThreadCore_Click_38異常");
23                             }
24                             Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
25                         }));
26                     }
27                     //多線程裏面拋出的異常,會終結當前線程;可是不會影響別的線程;
28                     //那線程異常哪裏去了? 被吞了,
29                     //假如我想獲取異常信息,還須要通知別的線程
30                     Task.WaitAll(taskList.ToArray());//1 能夠捕獲到線程的異常
31                 }
32                 catch (AggregateException aex)//2 須要try-catch-AggregateException
33                 {
34                     foreach (var exception in aex.InnerExceptions)
35                     {
36                         Console.WriteLine(exception.Message);
37                     }
38                 }
39                 catch (Exception ex)//能夠多catch  先具體再所有
40                 {
41                     Console.WriteLine(ex);
42                 }
43                 //線程異常後常常是須要通知別的線程,而不是等到WaitAll,問題就是要線程取消
44                 //工做中常規建議:多線程的委託裏面不容許異常,包一層try-catch,而後記錄下來異常信息,完成須要的操做
45             }
46             #endregion
View Code

多線程裏面拋出的異常,會終結當前線程;可是不會影響別的線程;線程異常哪裏去了? 被吞了併發

多線程的委託裏面不容許異常,包一層try-catch,而後記錄下來異常信息 ,通知別的線程async

線程取消分佈式

 1               {
 2                 CancellationTokenSource cts = new CancellationTokenSource();
 3                 var token = cts.Token; cts.Cancel();
 4                 CancellationTokenSource cts2 = new CancellationTokenSource();
 5                 var token2 = cts2.Token;
 6                 List<Task> taskList = new List<Task>();
 7                 for (int i = 0; i < 10; i++)
 8                 {
 9                     int k = i;
10                     switch (i%5)
11                     {
12                         case 0:
13                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=0"); }));break;
14                         case 1: 
15                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=1"); },token)); break;
16                         case 2: 
17                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=2"); }, token2)); break;
18                         case 3:
19                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=3"); })); break;
20                         case 4:
21                             taskList.Add(Task.Run(() => { Console.WriteLine($"i={i},k={k},i%5=4");
22                                 throw new Exception("throw new Exception");
23                             })); break;
24                     }
25                 }
26                 //Thread.Sleep(500);
27                 cts2.Cancel();
28                 try
29                 {
30                     Task.WaitAll(taskList.ToArray());
31                 }catch(AggregateException ae)
32                 {
33                     foreach (var item in ae.InnerExceptions)
34                     {
35                         Console.WriteLine($"{item.GetType().Name}:{item.Message}");
36                     }
37                 }
38                 Console.WriteLine("**********************************");
39                 foreach (var item in taskList)
40                 {
41                     Console.WriteLine($"Id:{item.Id},Status:{item.Status}");
42                     if (item.Exception != null)
43                     {
44                         foreach (var ex in item.Exception.InnerExceptions)
45                         {
46                             Console.WriteLine($"{ex.GetType().Name}:{ex.Message}");
47                         }
48                     }
49                 }
50             }
View Code

運行上面的代碼,有四個任務被取消,取消註釋,則有兩個任務被取消ide

線程安全

若是你的代碼在進程中有多個線程同時運行這一段,若是每次運行的結果都跟單線程運行時的結果一致,那麼就是線程安全的
線程安全問題通常都是有全局變量/共享變量/靜態變量/硬盤文件/數據庫的值,只要多線程都能訪問和修改

Lock

一、Lock解決多線程衝突

Lock是語法糖,Monitor.Enter,佔據一個引用,別的線程就只能等着 

推薦鎖是private static readonly object

A 不能是Null,能夠編譯不能運行;

B 不推薦lock(this),外面若是也要用實例,就衝突了

 1     public class LockHelper
 2     {
 3         public void Show()
 4         {
 5             LockTest test = new LockTest();
 6             Console.WriteLine(DateTime.Now);
 7             Task.Delay(10).ContinueWith(t =>
 8             {
 9                 lock (test)
10                 {
11                     Console.WriteLine($"*********Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
12                     Thread.Sleep(5000);
13                     Console.WriteLine($"*********End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
14                 }
15             });
16             test.LockThis();
17         }
18     }
19     public class LockTest
20     {
21         private int lockthis;
22         public void LockThis()
23         {
24             lock (this)
25             //遞歸調用,lock this  會不會死鎖? 不會死鎖!
26             //這裏是同一個線程,這個引用就是被這個線程所佔據
27             {
28                 Thread.Sleep(1000);
29                 this.lockthis++;
30                 if (this.lockthis < 10)
31                     this.LockThis();
32                 else
33                     Console.WriteLine($"This is  {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
34             }
35         }
36     }
View Code

這裏LockThis自身遞歸調用不會死鎖,這個引用被當前線程佔用,但當另外的實例要使用時就衝突了,必須等待LockThis執行完成後,釋放當前實例,外面的實例才能被調用

C 不該該是string; string在內存分配上是重用的,會衝突

 1             {
 2                 LockTest test = new LockTest();
 3                 Console.WriteLine(DateTime.Now);
 4                 string lockString = "lockString";
 5                 Task.Delay(1000).ContinueWith(t =>
 6                 {
 7                     lock (lockString)
 8                     {
 9                         Console.WriteLine($"****lockString Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
10                         Thread.Sleep(5000);
11                         Console.WriteLine($"****lockString End   {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}********");
12                     }
13                 });
14                 test.LockString();
15             }
16     public class LockTest
17     {
18         private int lockthis;
19         public void LockThis()
20         {
21             lock (this)
22             //遞歸調用,lock this  會不會死鎖? 不會死鎖!
23             //這裏是同一個線程,這個引用就是被這個線程所佔據
24             {
25                 Thread.Sleep(1000);
26                 this.lockthis++;
27                 if (this.lockthis < 10)
28                     this.LockThis();
29                 else
30                     Console.WriteLine($"This is  {nameof(LockThis)}:{this.lockthis} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
31             }
32         }
33 
34         private string lockString= "lockString";
35         public void LockString()
36         {
37             lock (lockString)
38             {
39                 Thread.Sleep(2000);
40                 Console.WriteLine($"This is  {nameof(LockString)}:{this.lockString} {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
41                 Thread.Sleep(2000);
42             }
43         }
44     }
View Code

String類型在內存分配上按享元模式設計的,某個字符串被佔用,其餘線程就必須等待字符串釋放後才能使用

D Lock裏面的代碼不要太多,這裏是單線程的

二、線程安全集合

System.Collections.Concurrent.ConcurrentQueue<T>

三、 數據分拆,避免多線程操做同一個數據;又安全又高效

 1         private int _sync = 0;
 2         private int _async = 0;
 3         private List<int> listInt = new List<int>();
 4         private static readonly object lockObject = new object();
 5         public void LockObject()
 6         {
 7             for (int i = 0; i < 1000; i++)
 8             {
 9                 this._sync++;
10             }
11             for (int i = 0; i < 1000; i++)
12             {
13                 Task.Run(() => this._async++);
14             }
15             for (int i = 0; i < 1000; i++)
16             {
17                 int k = i;
18                 Task.Run(() => this.listInt.Add(k));
19             }
20             Thread.Sleep(5 * 1000);
21             Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
22         }
View Code

運行上面的代碼發現_sync=1000  _async與listInt集合個數都少於1000

 1         public void LockObject()
 2         {
 3             for (int i = 0; i < 1000; i++)
 4             {
 5                 this._sync++;
 6             }
 7             for (int i = 0; i < 1000; i++)
 8             {
 9                 Task.Run(() => {
10                     lock (lockObject)
11                     {
12                         this._async++;
13                     }
14                 });
15             }
16             for (int i = 0; i < 1000; i++)
17             {
18                 int k = i;
19                 Task.Run(() => {
20                     lock (lockObject)
21                     {
22                         this.listInt.Add(k);
23                     }
24                 });
25             }
26             Thread.Sleep(5 * 1000);
27             Console.WriteLine($"_sync={this._sync} _async={this._async} listInt={this.listInt.Count}");
28         }
View Code

使用lock包裝後 _async與listInt集合個數都爲1000, 使用lock後 只有一個線程才能進入lock方法塊內,至關於把程序又變回了單線程

微軟文檔:

lock:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/lock-statement

CancellationTokenSource:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtokensource?view=netframework-4.8

CancellationToken:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8

相關文章
相關標籤/搜索