多線(xiàn)程整理
一、簡(jiǎn)介
1.1、進(jìn)程
當(dāng)一個(gè)程序開(kāi)始運(yùn)行時(shí),它就是一個(gè)進(jìn)程,進(jìn)程包括運(yùn)行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。 一個(gè)進(jìn)程是由多個(gè)線(xiàn)程組成。
1.2、線(xiàn)程
線(xiàn)程是程序中的一個(gè)執(zhí)行流,每個(gè)線(xiàn)程都有自己的專(zhuān)有寄存器(棧指針、程序計(jì)數(shù)器等),但代碼區(qū)是共享的,即不同的線(xiàn)程可以執(zhí)行同樣的函數(shù)。
1.3、句柄
句柄是Windows系統(tǒng)中對(duì)象或?qū)嵗臉?biāo)識(shí)。這些對(duì)象包括模塊、應(yīng)用程序?qū)嵗⒋翱凇⒖刂啤⑽粓D、GDI對(duì)象、資源、文件等。
1.4、多線(xiàn)程
1.4.1、概念
程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線(xiàn)程來(lái)執(zhí)行不同的任務(wù),也就是說(shuō)允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線(xiàn)程來(lái)完成各自的任務(wù)。
多線(xiàn)程一定是運(yùn)行在多核計(jì)算機(jī)上。用CPU運(yùn)行空間換取時(shí)間,CPU是分片執(zhí)行的。單核CPU上談多線(xiàn)程,都是扯淡。
1.4.2、優(yōu)點(diǎn)
提高CPU利用率。在多線(xiàn)程程序中,一個(gè)線(xiàn)程必須等待的時(shí)候,CPU可以運(yùn)行其它的線(xiàn)程而不是等待,這樣就大大提高了程序的效率。(犧牲空間計(jì)算資源,來(lái)?yè)Q取時(shí)間)
1.4.3、缺點(diǎn)
- 占用內(nèi)存多。線(xiàn)程也是程序,所以線(xiàn)程運(yùn)行需要占用計(jì)算機(jī)資源,線(xiàn)程越多占用資源也越多。
- 占用CPU多。多線(xiàn)程需要協(xié)調(diào)和管理,所以需要CPU跟蹤線(xiàn)程,消耗CPU資源。
- 多線(xiàn)程存在資源共享問(wèn)題。線(xiàn)程之間對(duì)共享資源的訪(fǎng)問(wèn)會(huì)相互影響,必須解決競(jìng)用共享資源的問(wèn)題。
- 管理麻煩,容易產(chǎn)生bug。線(xiàn)程太多會(huì)導(dǎo)致控制太復(fù)雜,最終可能造成很多Bug。
1.4.4、業(yè)務(wù)場(chǎng)景
- 主線(xiàn)程試圖執(zhí)行冗長(zhǎng)的耗時(shí)操作,導(dǎo)致系統(tǒng)界面卡頓,客戶(hù)體驗(yàn)較差。可以新開(kāi)線(xiàn)程處理冗長(zhǎng)的耗時(shí)操作。
- 請(qǐng)求別的數(shù)據(jù)庫(kù)服務(wù),業(yè)務(wù)服務(wù)等。新開(kāi)一個(gè)線(xiàn)程,讓主線(xiàn)程繼續(xù)干別的事。
- 利用多線(xiàn)程拆分復(fù)雜運(yùn)算,提高計(jì)算速度。
1.4.5、不建議使用多線(xiàn)程的業(yè)務(wù)場(chǎng)景
當(dāng)單線(xiàn)程能很好處理問(wèn)題,就不要使用多線(xiàn)程。
1.5、同步/異步
1.5.1、同步方法
線(xiàn)性執(zhí)行,從上往下依次執(zhí)行,同步方法執(zhí)行慢,消耗的計(jì)算機(jī)資源少。
1.5.2、異步方法
線(xiàn)程和線(xiàn)程之間,不再線(xiàn)型執(zhí)行,多個(gè)線(xiàn)程總的耗時(shí)少,執(zhí)行快,消耗的計(jì)算機(jī)資源多,各線(xiàn)程執(zhí)行是無(wú)序的。
二、C#中的多線(xiàn)程
2.1、Thread
最早的多線(xiàn)程處理方式,.NET 1.0時(shí)代,逐漸被微軟拋棄,微軟強(qiáng)推Task。
2.1.1、前臺(tái)線(xiàn)程/后臺(tái)線(xiàn)程
前臺(tái)線(xiàn)程:界面關(guān)閉,線(xiàn)程隨之消失。
后臺(tái)線(xiàn)程:界面關(guān)閉,線(xiàn)程繼續(xù)執(zhí)行,完畢后才結(jié)束。
2.2、其他關(guān)鍵字
線(xiàn)程優(yōu)先級(jí)、數(shù)據(jù)槽、內(nèi)存柵欄
2.2、 ThreadPool
線(xiàn)程池:不需要程序員對(duì)線(xiàn)程的數(shù)量管控,提高性能,防止濫用,去掉了很多在Thread中沒(méi)有必要的Api。
2.3、Task
Task出現(xiàn)之前,微軟的多線(xiàn)程處理方式有:Thread→ThreadPool→委托的異步調(diào)用。.NET 4.0,在ThreadPool的基礎(chǔ)上進(jìn)行的封裝,Task的控制和擴(kuò)展性很強(qiáng),在線(xiàn)程的延續(xù)、阻塞、取消、超時(shí)等方面遠(yuǎn)勝于Thread和ThreadPool。
2.3.1、開(kāi)啟線(xiàn)程方式
1)、new Task().Start()

2)、Task.Run()

3)、Task.Factory.StartNew()

4)、new Task().RunSynchronously()(同步方式,上面三種異步方式)

2.3.2、線(xiàn)程等待
1、task.Wait()
等待task內(nèi)部執(zhí)行完畢,才會(huì)往后直行,卡主線(xiàn)程,Task實(shí)例方法。
task.Wait(1000);//等待1000毫秒后就往后執(zhí)行,不管有沒(méi)有執(zhí)行結(jié)束。
task.Wait(TimeSpan.FromMilliseconds(1000));//等待1000毫秒后就往后執(zhí)行,不管有沒(méi)有執(zhí)行結(jié)束。

2、WaitAny
某一個(gè)任務(wù)執(zhí)行結(jié)束后,去觸發(fā)一個(gè)動(dòng)作,卡主線(xiàn)程,Task靜態(tài)方法。數(shù)據(jù)有可能是來(lái)自于第三方接口,緩存,數(shù)據(jù)庫(kù),查詢(xún)的時(shí)候,我們不確定,開(kāi)啟幾個(gè)線(xiàn)程同時(shí)查詢(xún),只要一個(gè)返回了就返回界面。

3、WaitAll
所有任務(wù)執(zhí)行完成后,去觸發(fā)一個(gè)動(dòng)作,卡主線(xiàn)程,Task靜態(tài)方法
數(shù)據(jù)是來(lái)自于第三方接口,緩存,數(shù)據(jù)庫(kù),查詢(xún)的時(shí)候,開(kāi)啟幾個(gè)線(xiàn)程同時(shí)查詢(xún),等所有數(shù)據(jù)全部查詢(xún)出來(lái),一起返回界面

4、WhenAny
與下面ContinueWith配合執(zhí)行,當(dāng)傳入的線(xiàn)程中任何一個(gè)線(xiàn)程執(zhí)行完畢,繼續(xù)執(zhí)行ContinueWith中的任務(wù)(屬于開(kāi)啟新線(xiàn)程,不卡主線(xiàn)程),Task靜態(tài)方法。

5、WhenAll
當(dāng)其中所有線(xiàn)程執(zhí)行完成后,新開(kāi)啟了一個(gè)線(xiàn)程執(zhí)行,繼續(xù)執(zhí)行新業(yè)務(wù),所以執(zhí)行過(guò)程中,不卡主線(xiàn)程,Task靜態(tài)方法。
List<Task> taskList = new List<Task>(); TaskFactory factory = new TaskFactory(); taskList.Add(factory.StartNew(() => { Debug.WriteLine("查詢(xún)數(shù)據(jù)庫(kù)"); })); taskList.Add(factory.StartNew(() => { Debug.WriteLine("查詢(xún)緩存"); })); taskList.Add(factory.StartNew(() => { Debug.WriteLine("查詢(xún)接口"); })); Task.WhenAll(taskList.ToArray()).ContinueWith((n) => { Debug.WriteLine($"查到數(shù)據(jù),返回界面!"); });
6、ContinueWhenAny
某一個(gè)任務(wù)執(zhí)行結(jié)束后,去觸發(fā)一個(gè)動(dòng)作,不卡主線(xiàn)程,TaskFactory實(shí)例方法,等價(jià)于WhenAny+ContinueWith
List<Task> taskList = new List<Task>(); TaskFactory factory = new TaskFactory(); taskList.Add(factory.StartNew(obj => Coding("張三", "數(shù)據(jù)庫(kù)設(shè)計(jì)"), "張三")); taskList.Add(factory.StartNew(obj => Coding("李四", "接口對(duì)接"), "李四")); taskList.Add(factory.StartNew(obj => Coding("王五", "Webapi"), "王五")); taskList.Add(factory.StartNew(obj => Coding("趙六", "前端頁(yè)面"), "趙六")); factory.ContinueWhenAny(taskList.ToArray(), ts => { Debug.WriteLine($"{ts.AsyncState}同學(xué)開(kāi)發(fā)完畢,田七開(kāi)始測(cè)試!"); });
7、ContinueWhenAll
所有任務(wù)執(zhí)行完成后,去觸發(fā)一個(gè)動(dòng)作,不卡主線(xiàn)程,TaskFactory實(shí)例方法,等價(jià)于WhenAll+ContinueWith
List<Task> taskList = new List<Task>(); TaskFactory factory = new TaskFactory(); taskList.Add(factory.StartNew(obj => Coding("張三", "數(shù)據(jù)庫(kù)設(shè)計(jì)"), "張三")); taskList.Add(factory.StartNew(obj => Coding("李四", "接口對(duì)接"), "李四")); taskList.Add(factory.StartNew(obj => Coding("王五", "Webapi"), "王五")); taskList.Add(factory.StartNew(obj => Coding("趙六", "前端頁(yè)面"), "趙六")); factory.ContinueWhenAll(taskList.ToArray(), ts => { Debug.WriteLine($"所有人開(kāi)發(fā)完畢,我們一起慶祝一下吃個(gè)飯!"); });
2.3.3、TaskCreationOptions枚舉類(lèi)詳解
一個(gè)Task內(nèi)部,可以開(kāi)啟線(xiàn)程,Task內(nèi)部的線(xiàn)程可以理解為子線(xiàn)程,Task為父線(xiàn)程,創(chuàng)建Task實(shí)例的時(shí)候可以傳入TaskCreationOptions枚舉參數(shù)來(lái)影響線(xiàn)程的運(yùn)行方式
1、None,默認(rèn)情況
父線(xiàn)程不會(huì)等待子線(xiàn)程執(zhí)行結(jié)束才結(jié)束。
Task task = new Task(() => { Debug.WriteLine($"task--start--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}"); Task task1 = new Task(() => { this.DoSomething("task1"); }); Task task2 = new Task(() => { this.DoSomething("task2"); }); task1.Start(); task2.Start(); Debug.WriteLine($"task--end--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}"); }); task.Start(); task.Wait();
2、AttachedToParent
子線(xiàn)程附加到父線(xiàn)程,父線(xiàn)程必須等待所有子線(xiàn)程執(zhí)行結(jié)束才能結(jié)束,相當(dāng)于Task.WaitAll(task1, task2)。
Task task = new Task(() => { Debug.WriteLine($"task--start--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}"); Task task1 = new Task(() => { this.DoSomething("task1"); }, TaskCreationOptions.AttachedToParent); Task task2 = new Task(() => { this.DoSomething("task2"); }, TaskCreationOptions.AttachedToParent); task1.Start(); task2.Start(); Debug.WriteLine($"task--end--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}"); }); task.Start(); task.Wait();
3、DenyChildAttach
不允許子任務(wù)附加到父任務(wù)上,反AttachedToParent,和默認(rèn)效果一樣
Task task = new Task(() => { Debug.WriteLine($"task--start--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}"); Task task1 = new Task(() => { this.DoSomething("task1"); }, TaskCreationOptions.AttachedToParent); Task task2 = new Task(() => { this.DoSomething("task2"); }, TaskCreationOptions.AttachedToParent); task1.Start(); task2.Start(); Debug.WriteLine($"task--end--{Thread.CurrentThread.ManagedThreadId.ToString("00")}--{DateTime.Now.ToString("HH:mm:ss.fff")}"); }, TaskCreationOptions.DenyChildAttach); task.Start(); task.Wait();
4、PreferFairness
相對(duì)來(lái)說(shuō)比較公平執(zhí)行的先申請(qǐng)的線(xiàn)程優(yōu)先執(zhí)行
Task task1 = new Task(() => { this.DoSomething("task1"); }, TaskCreationOptions.PreferFairness); Task task2 = new Task(() => { this.DoSomething("task2"); }, TaskCreationOptions.PreferFairness); task1.Start(); task2.Start();
5、LongRunning
事先知道是長(zhǎng)時(shí)間執(zhí)行的線(xiàn)程就加這個(gè)參數(shù),線(xiàn)程調(diào)度會(huì)優(yōu)化
6、RunContinuationsAsynchronously
強(qiáng)制以異步方式執(zhí)行添加到當(dāng)前任務(wù)的延續(xù)。
7、HideScheduler
防止環(huán)境計(jì)劃程序被視為已創(chuàng)建任務(wù)的當(dāng)前計(jì)劃程序。 這意味著像 StartNew 或 ContinueWith 創(chuàng)建任務(wù)的執(zhí)行操作將被視為 System.Threading.Tasks.TaskScheduler.Default當(dāng)前計(jì)劃程序。
8、TaskContinuationOptions枚舉類(lèi)詳解
ContinueWith可以傳入TaskContinuationOptions枚舉類(lèi)參數(shù)來(lái)影響線(xiàn)程的運(yùn)行方式。
(1)、None,默認(rèn)情況
任務(wù)順序執(zhí)行
Task task1 = new Task(() => { this.DoSomething("task1"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }); Task task3=task2.ContinueWith(t => { this.DoSomething("task3"); }); task1.Start();
(2)、LazyCancellation
取消該線(xiàn)程,該線(xiàn)程的前一個(gè)線(xiàn)程和后一個(gè)線(xiàn)程順序執(zhí)行。
CancellationTokenSource source = new CancellationTokenSource(); source.Cancel(); Task task1 = new Task(() => { this.DoSomething("task1"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, source.Token,TaskContinuationOptions.LazyCancellation, TaskScheduler.Current); Task task3 = task2.ContinueWith(t => { this.DoSomething("task3"); }); task1.Start();
(3)、ExecuteSynchronously
前后任務(wù)由同一個(gè)線(xiàn)程執(zhí)行
Task task1 = new Task(() => { this.DoSomething("task1"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); },TaskContinuationOptions.ExecuteSynchronously); task1.Start();
(4)、NotOnRanToCompletion
Task task1 = new Task(() => { this.DoSomething("task1"); //異常了,表示未執(zhí)行完成,task2能執(zhí)行 //不異常,表示執(zhí)行完成,task2不能執(zhí)行 throw new Exception("手動(dòng)制造異常,表示不能執(zhí)行完畢"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, TaskContinuationOptions.NotOnRanToCompletion); task1.Start();
(5)、OnlyOnRanToCompletion
延續(xù)任務(wù)必須在前面task完成狀態(tài)才能執(zhí)行,和NotOnRanToCompletion正好相反
Task task1 = new Task(() => { this.DoSomething("task1"); //異常了,表示未執(zhí)行完成,task2不能執(zhí)行 //不異常,表示執(zhí)行完成,task2能執(zhí)行 throw new Exception("手動(dòng)制造異常,表示不能執(zhí)行完畢"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, TaskContinuationOptions.OnlyOnRanToCompletion); task1.Start();
(6)、NotOnFaulted
延續(xù)任務(wù)必須在前面task完成狀態(tài)才能執(zhí)行,效果和OnlyOnRanToCompletion差不多。
Task task1 = new Task(() => { this.DoSomething("task1"); //throw new Exception("手動(dòng)制造異常"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, TaskContinuationOptions.NotOnFaulted); task1.Start();
(7)、OnlyOnFaulted
延續(xù)任務(wù)必須在前面task未完成狀態(tài)才能執(zhí)行,效果和NotOnRanToCompletion差不多。
Task task1 = new Task(() => { this.DoSomething("task1"); //throw new Exception("手動(dòng)制造異常"); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, TaskContinuationOptions.OnlyOnFaulted); task1.Start();
(8)、OnlyOnCanceled
前面的任務(wù)未被取消才執(zhí)行后面的任務(wù)。
CancellationTokenSource cts = new CancellationTokenSource(); Task task1 = new Task(() => { this.DoSomething("task1"); cts.Cancel(); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, TaskContinuationOptions.OnlyOnCanceled); task1.Start();
(9)、NotOnCanceled
前面的任務(wù)被取消才執(zhí)行后面的任務(wù)
CancellationTokenSource cts = new CancellationTokenSource(); Task task1 = new Task(() => { this.DoSomething("task1"); cts.Cancel(); }); Task task2 = task1.ContinueWith(t => { this.DoSomething("task2"); }, TaskContinuationOptions.NotOnCanceled); task1.Start();
(10)、PreferFairness
System.Threading.Tasks.TaskScheduler 以一種盡可能公平的方式安排任務(wù),這意味著較早安排的任務(wù)將更可能較早運(yùn)行,而較晚安排運(yùn)行的任務(wù)將更可能較晚運(yùn)行。
(11)、LongRunning
指定某個(gè)任務(wù)將是運(yùn)行時(shí)間長(zhǎng)、粗粒度的操作。 它會(huì)向 System.Threading.Tasks.TaskScheduler 提示,過(guò)度訂閱可能是合理的。
(12)、AttachedToParent
指定將任務(wù)附加到任務(wù)層次結(jié)構(gòu)中的某個(gè)父級(jí)。
(13)、DenyChildAttach
如果嘗試附有子任務(wù)到創(chuàng)建的任務(wù),指定 System.InvalidOperationException 將被引發(fā)。
(14)、HideScheduler
防止環(huán)境計(jì)劃程序被視為已創(chuàng)建任務(wù)的當(dāng)前計(jì)劃程序。 這意味著像 StartNew 或 ContinueWith 創(chuàng)建任務(wù)的執(zhí)行操作將被視為 System.Threading.Tasks.TaskScheduler.Default當(dāng)前計(jì)劃程序。
9、延遲執(zhí)行
Task.Delay(),一般和ContinueWith配合使用,執(zhí)行的動(dòng)作就是ContinueWith內(nèi)部的委托,委托的執(zhí)行有可能是一個(gè)全新的線(xiàn)程,也有可能是主線(xiàn)程。
//開(kāi)啟線(xiàn)程后,線(xiàn)程等待3000毫秒后執(zhí)行動(dòng)作,不卡主線(xiàn)程 Task.Delay(3000).ContinueWith(t => { this.DoSomething("張三"); });
2.3.4、Task進(jìn)階
1、多線(xiàn)程捕獲異常
1)、線(xiàn)程不等待,捕捉不到異常
多線(xiàn)程中,如果發(fā)生異常,使用try-catch包裹,捕捉不到異常,異常還沒(méi)發(fā)生,主線(xiàn)程已經(jīng)執(zhí)行結(jié)束。
//捕捉不到異常 try { Task task = Task.Run(() => { int i = 0; int j = 10; int k = j / i; //嘗試除以0,會(huì)異常 }); } catch (AggregateException aex) { foreach (var exception in aex.InnerExceptions) { Debug.WriteLine($"線(xiàn)程不等待:異常{exception.Message}"); } }
2)、線(xiàn)程不等待,線(xiàn)程內(nèi)部捕獲異常
多線(xiàn)程中,如果要捕捉異常,可以在線(xiàn)程內(nèi)部try-catch,可以捕捉到異常。
//捕捉到異常 try { Task task = Task.Run(() => { try { int i = 0; int j = 10; int k = j / i; //嘗試除以0,會(huì)異常 } catch (Exception ex) { Debug.WriteLine($"線(xiàn)程內(nèi)異常{ex.Message}"); } }); } catch (AggregateException aex) { foreach (var exception in aex.InnerExceptions) { Debug.WriteLine($"線(xiàn)程不等待:異常{exception.Message}"); } }
運(yùn)行結(jié)果
線(xiàn)程內(nèi)異常Attempted to divide by zero.
3)、線(xiàn)程等待,能夠捕獲異常
-
-
-
-
多線(xiàn)程中,如果要捕捉異常,需要設(shè)置主線(xiàn)程等待子線(xiàn)程執(zhí)行結(jié)束,可以捕捉到異常
-
多線(xiàn)程內(nèi)部發(fā)生異常后,拋出的異常類(lèi)型是system.AggregateException
-
-
-
//捕捉到異常 try { Task task = Task.Run(() => { int i = 0; int j = 10; int k = j / i; //嘗試除以0,會(huì)異常 }); //線(xiàn)程等待 task.Wait(); } catch (AggregateException aex) { foreach (var exception in aex.InnerExceptions) { Debug.WriteLine($"線(xiàn)程等待:異常{exception.Message}"); } }
運(yùn)行結(jié)果:
線(xiàn)程等待:異常Attempted to divide by zero.
2、線(xiàn)程取消
線(xiàn)程取消是不能從外部取消的,線(xiàn)程取消的實(shí)質(zhì)還是通過(guò)變量去控制程序的運(yùn)行和結(jié)束,正常結(jié)束,或者發(fā)生異常結(jié)束
其他參考:鏈接
posted on 2024-04-15 17:00 木乃伊人 閱讀(38) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)