一、Task類
Task是.NET Framework 3.0出現的,執行緒是基于執行緒池的,然后提供了豐富的API,Task被稱之為多執行緒的最佳實踐,
首先我們來看下如何使用Task來啟動執行緒:
/// <summary> /// 一個比較耗時耗資源的私有方法 /// </summary> private void DoSomethingLong(string name) { Console.WriteLine($"****************DoSomethingLong Start {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Thread.Sleep(2000); Console.WriteLine($"****************DoSomethingLong End {name} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
/// <summary> /// Task是.NET Framework 3.0出現的,執行緒是基于執行緒池的,然后提供了豐富的API, /// </summary> private void btnTask_Click(object sender, EventArgs e) { Console.WriteLine($"****************btnTask_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); { Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1")); task.Start(); } { Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2")); } { TaskFactory taskFactory = Task.Factory; Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3")); } Console.WriteLine($"****************btnTask_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); }
Task的執行緒是源于執行緒池,執行緒池是單例的,全域唯一的,
//Task的執行緒是源于執行緒池 //執行緒池是單例的,全域唯一的 { //設定的最大值,必須大于CPU核數,否則設定無效 //全域的,請不要這樣設定!!!此處設定只是為了演示 ThreadPool.SetMaxThreads(12, 12); //設定后,同時并發的Task只有12個;而且執行緒是復用的; for (int i = 0; i < 100; i++) { int k = i; Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); }); } //假如說我想控制下Task的并發數量,該怎么做? }
注意:執行緒池的執行緒數量,設定的最大值,必須大于CPU核數,否則設定無效,
運行結果如下:

從結果中可以看出同時并發的Task只有12個(執行緒ID從03到14);而且執行緒是復用的;
同步等待:
{ Stopwatch stopwatch = new Stopwatch(); //計時 stopwatch.Start(); Console.WriteLine("在Sleep之前"); Thread.Sleep(2000);//同步等待--當前執行緒等待2s 然后繼續 Console.WriteLine("在Sleep之后"); stopwatch.Stop(); Console.WriteLine($"Sleep耗時{stopwatch.ElapsedMilliseconds}"); }
異步等待(Task.Delay方法,一般結合ContinueWith一起用),如下所示:
{ Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); Console.WriteLine("在Delay之前"); Task task = Task.Delay(2000) //異步等待--等待2s后啟動新任務 .ContinueWith(t => { stopwatch.Stop(); Console.WriteLine($"Delay耗時{stopwatch.ElapsedMilliseconds}"); Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); Console.WriteLine("在Delay之后"); //stopwatch.Stop(); //Console.WriteLine($"Delay耗時{stopwatch.ElapsedMilliseconds}"); }
什么時候能用多執行緒? 任務能并發的時候,多執行緒能干嘛?提升速度/優化用戶體驗,
ContinueWhenAny:同時并發多個任務,當其中任意一個任務完成的時候,也可能是多個任務同時第一時間完成的時候,執行后續的任務,非阻塞式的回呼,
ContinueWhenAll:同時并發多個任務,當所有的任務都完成的時候,執行后續的任務,非阻塞式的回呼,
WaitAny:阻塞當前執行緒,等著任意一個任務完成,
WaitAll:阻塞當前執行緒,等著全部任務完成,
/// <summary> /// 模擬講課 /// </summary> private void Teach(string lesson) { Console.WriteLine($"{lesson}開始講,,,"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Console.WriteLine($"{lesson}講完了,,,"); } /// <summary> /// 模擬編程程序 /// </summary> private void Coding(string name, string projectName) { Console.WriteLine($"****************Coding Start {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); long lResult = 0; for (int i = 0; i < 1_000_000_000; i++) { lResult += i; } Console.WriteLine($"****************Coding End {name} {projectName} {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************"); }
{ //什么時候能用多執行緒? 任務能并發的時候 //多執行緒能干嘛?提升速度/優化用戶體驗 Console.WriteLine("Eleven開啟了一學期的課程"); this.Teach("Lesson1"); this.Teach("Lesson2"); this.Teach("Lesson3"); //不能并發,因為有嚴格順序(只有Eleven講課) Console.WriteLine("部署一下專案實戰作業,需要多人合作完成"); //開發可以多人合作---多執行緒--提升性能 TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(() => this.Coding("張三", "Portal"))); taskList.Add(taskFactory.StartNew(() => this.Coding("李四", "DBA "))); taskList.Add(taskFactory.StartNew(() => this.Coding("王五", "Client"))); taskList.Add(taskFactory.StartNew(() => this.Coding("趙六", "BackService"))); taskList.Add(taskFactory.StartNew(() => this.Coding("錢七", "Wechat"))); //誰第一個完成,獲取一個紅包獎勵 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX開發完成,獲取個紅包獎勵" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); //實戰作業完成后,一起慶祝一下 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"開發都完成,一起慶祝一下" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}"))); //ContinueWhenAny ContinueWhenAll 非阻塞式的回呼;而且使用的執行緒可能是新執行緒,也可能是剛完成任務的執行緒,唯一不可能是主執行緒, //阻塞當前執行緒,等著任意一個任務完成 Task.WaitAny(taskList.ToArray());//也可以限時等待 Console.WriteLine("Eleven準備環境開始部署"); //需要能夠等待全部執行緒完成任務再繼續 阻塞當前執行緒,等著全部任務完成 Task.WaitAll(taskList.ToArray()); Console.WriteLine("5個模塊全部完成后,Eleven集中點評"); //Task.WaitAny WaitAll都是阻塞當前執行緒,等任務完成后執行操作 //阻塞卡界面,是為了并發以及順序控制 //網站首頁:A資料庫 B介面 C分布式服務 D搜索引擎,適合多執行緒并發,都完成后才能回傳給用戶,需要等待WaitAll //串列頁:核心資料可能來自資料庫/介面服務/分布式搜索引擎/快取,多執行緒并發請求,哪個先完成就用哪個結果,其他的就不管了 }
啟動帶有回呼和帶有回傳值的執行緒:
{ //帶有回呼 Task.Run(() => this.DoSomethingLong("btnTask_Click")).ContinueWith(t => Console.WriteLine($"btnTask_Click已完成" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); } { //帶有回傳值 Task<int> result = Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }); int i = result.Result;//會阻塞 }
控制Task的并發數量,該怎么做?(下文會介紹更好的解決方案)
{ //假如說我想控制下Task的并發數量,該怎么做? 20個 List<Task> taskList = new List<Task>(); for (int i = 0; i < 10000; i++) { int k = i; if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 20) { Task.WaitAny(taskList.ToArray()); taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList(); } taskList.Add(Task.Run(() => { Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Thread.Sleep(2000); })); } }
想使用回傳值但又不想阻塞執行緒怎么辦?
//想使用回傳值但又不想阻塞執行緒怎么辦? { Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }).ContinueWith(tInt => { int i = tInt.Result; //不會阻塞 }); //不會阻塞 Task<int> result = Task.Run<int>(() => { Thread.Sleep(2000); return DateTime.Now.Year; }); Task.Run(() => { int i = result.Result; }); }
如果在啟動執行緒的時候想帶一些引數資訊,則可以使用對應的多載方法:
{ TaskFactory taskFactory = new TaskFactory(); List<Task> taskList = new List<Task>(); taskList.Add(taskFactory.StartNew(o => this.Coding("張三", "Portal"), "張三")); taskList.Add(taskFactory.StartNew(o => this.Coding("李四", " DBA "), "李四")); taskList.Add(taskFactory.StartNew(o => this.Coding("王五", "Client"), "王五")); taskList.Add(taskFactory.StartNew(o => this.Coding(" 趙六", "BackService"), " 趙六")); taskList.Add(taskFactory.StartNew(o => this.Coding("錢七", "Wechat"), "錢七")); //誰第一個完成,獲取一個紅包獎勵 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"{t.AsyncState}開發完成,獲取個紅包獎勵" + $"{Thread.CurrentThread.ManagedThreadId.ToString("00")}")); }
幾乎90%以上的多執行緒場景,以及順序控制,以上的Task的方法就可以完成,
如果你的多執行緒場景太復雜搞不定,那么請梳理一下你的流程,簡化一下,
建議最好不要執行緒嵌套執行緒,兩層勉強能懂,三層hold不住的,更多只能求神,
二、Parallel類
Parallel可以啟動多個執行緒去并發執行多個Action,它是多執行緒的,Parallel最直觀的特點是主執行緒(當前執行緒)也會參與計算---阻塞界面(主執行緒忙于計算,無暇他顧),
Parallel是在Task的基礎上做了一個封裝,它的效果等于TaskWaitAll+主執行緒(當前執行緒)參與計算,
/// <summary> /// Parallel /// </summary> private void btnParallel_Click(object sender, EventArgs e) { Console.WriteLine($"****************btnParallel_Click Start {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); { //Parallel并發執行多個Action 多執行緒的 //主執行緒(當前執行緒)會參與計算---阻塞界面 //等于TaskWaitAll+主執行緒計算 Parallel.Invoke( () => this.DoSomethingLong("btnParallel_Click_1"), () => this.DoSomethingLong("btnParallel_Click_2"), () => this.DoSomethingLong("btnParallel_Click_3"), () => this.DoSomethingLong("btnParallel_Click_4"), () => this.DoSomethingLong("btnParallel_Click_5")); } Console.WriteLine($"****************btnParallel_Click End {Thread.CurrentThread.ManagedThreadId.ToString("00")} " + $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************"); }
運行結果如下:

可以看出執行緒ID為01的主執行緒(當前執行緒)也參與計算了,
Parallel其他的API如下所示:
//主執行緒(當前執行緒)會參與計算 { Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}")); } //主執行緒(當前執行緒)會參與計算 { Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}")); }
如何控制并發(執行緒)數量?上面我們在介紹Task的時候已經寫了一種解決方案,其實還有一種更好的解決方案,就是使用Parallel來實作,
//控制執行緒數量(并發數量) //會阻塞當前執行緒(主執行緒)-- 卡界面 { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); } //控制執行緒數量(并發數量) //不會阻塞當前執行緒(主執行緒) { Task.Run(() => { ParallelOptions options = new ParallelOptions(); options.MaxDegreeOfParallelism = 3; Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}")); }); }
Demo原始碼:
鏈接:https://pan.baidu.com/s/1SpbyPnohojyakxCOu4S9DA 提取碼:mwd1
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/13556424.html
著作權宣告:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/1606.html
標籤:ASP.NET
上一篇:自定義函式解決JS計算尾差
