前言
在上一篇文檔《C# 實現線程的常用幾種方式》中記錄了在C#使用多線程的常用幾種實現方式,相對來說,Task才是多線程的最佳實踐,那到底其他方式到底優缺點,而Task的優勢有哪些?下面簡單總結一下:
Thread 類方式:
優點:提供操作線程的API的多;能根據自己需要創建對應的線程;
缺點:頻繁的創建和消耗比較好資源;提供操作線程的API不是馬上響應(線程是操作系統統一管理,收到指令之后,具體還得操作系統真實處理,而操作系統收到指令之后並非馬上執行相關指令);
TreadPool 池化方式:
優點:池化線程進行管理,需要使用就從池中獲取就行,避免頻繁創建和銷毀線程;從而可以達到線程的復用;
缺點:提供的API太少,線程等待順序控制比較弱;從而在一些業務情況下操作不方便;
Task:
優點:在ThreadPool的思想進行了封裝,繼承了ThreadPool的優點;提供了豐富的線程控制API,從而方便了開發;
Task使用--創建任務的幾種方式
方式一:
#region Task創建方式1 //創建任務 線程離不開委托,因為需要處理業務,不然開啟線程干嘛 Task task = new Task(() => { Console.WriteLine($"Task 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(2000); }); //開啟任務 task.Start(); #endregion
方式二:
#region Task創建方式2 Task task1 = Task.Run(() => { Console.WriteLine($"Task 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(2000); }); #endregion
方式三:
#region Task創建方式3 Task task2 = Task.Factory.StartNew(() => { Console.WriteLine($"Task 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(2000); }); #endregion
Task使用--常用實例API
常規正常流程:
static void Main(string[] args) { Console.WriteLine($"主線程{Thread.CurrentThread.ManagedThreadId}開啟"); var task = Task.Run(() => { Console.WriteLine($"Task 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(2000); }); Console.WriteLine($"主線程{Thread.CurrentThread.ManagedThreadId}完成"); Console.ReadKey(); }
運行結果:
實例方法.Wait()
.Wait() 等待執行調用任務完成,然后執行下一步; 及阻塞了主線程;
實例方法.ContinueWith()
ContinueWith() 等調用者結束之后才進行調用里面的相關業務,由線程池分配線程進行處理接下來的業務,不阻塞主線程,但卻能控制業務之間的先后順序;
Task使用--Task中的靜態API
Task.WaitAll
Task.WaitAll等到所有任務都完成之后,才進行主線程的下一步操作,即阻塞主線程;
Task.WaitAny
Task.WaitAny等到其中一個任務都完成之后,才進行主線程的下一步操作,其中任務沒有完成之前也阻塞主線程;
Task使用--Task.Factory中常用API
Task.Factory.ContinueWhenAll
當ContinueWhenAll中所有任務都完成時執行回調方法,不阻塞主線程:
static void Main(string[] args) { Console.WriteLine($"主線程{Thread.CurrentThread.ManagedThreadId}開啟"); var task1 = Task.Run(() => { Console.WriteLine($"Task1 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(2000); }); var task2 = Task.Run(() => { Console.WriteLine($"Task2 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(3000); }); var task3 = Task.Run(() => { Console.WriteLine($"Task3 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(5000); }); List<Task> listTask = new List<Task> { task1, task2, task3 }; Task.Factory.ContinueWhenAll(listTask.ToArray(),tasks=> { Console.WriteLine($"任務執行結束"); }); Console.WriteLine($"主線程{Thread.CurrentThread.ManagedThreadId}完成"); Console.ReadKey(); }
執行結果:
如上圖,不阻塞主線程, 當封裝在所有list中的任務全部執行完成之后,再進行后續的處理,其中后續處理的業務的參數是上一完成任務的列表!!!
注: 子線程開啟時,線程之間的順序是不可控制的,由操作系統根據資源情況進行分配處理;
Task.Factory.ContinueWhenAny
當參數中的任務有一個完成之后就進行回調,執行下一個任務。
static void Main(string[] args) { Console.WriteLine($"主線程{Thread.CurrentThread.ManagedThreadId}開啟"); var task1 = Task.Run(() => { Console.WriteLine($"Task1 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(2000); }); var task2 = Task.Run(() => { Console.WriteLine($"Task2 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(3000); }); var task3 = Task.Run(() => { Console.WriteLine($"Task3 開啟線程{Thread.CurrentThread.ManagedThreadId}處理業務"); Thread.Sleep(5000); }); List<Task> listTask = new List<Task> { task1, task2, task3 }; Task.Factory.ContinueWhenAny(listTask.ToArray(), tasks => { Console.WriteLine($"其中一個任務執行結束"); }); Console.WriteLine($"主線程{Thread.CurrentThread.ManagedThreadId}完成"); Console.ReadKey(); }
執行結果:
Task.Factory.ContinueWhenAny方法等其中的任務有一項完成之后就立即返回,調用后續業務,不阻塞主線程; 線程在運行過程中,之間的順序是不可控的。
CancellationTokenSource 取消任務
在任務執行的過程中,常常會有需求進行任務的取消,有的會借用公共第三方變量進行任務控制是否繼續執行,如:true執行,false不執行;在Task中,提供了CancellationTokenSource進行任務取消。如下:
如上代碼說明,在task1執行完成之后,主動調用Cancel方法取消任務,task2就檢測到任務取消,即退出;
自動取消:
如上圖,通過指定一個時間,然后時間到了自動取消任務,也可以使用cancelTokenSource.CancelAfter(3000); 這樣也能實現自動取消;
總結
以上記錄一些Task常用的API,但是沒有具體每個參數都進行記錄,但主要功能已經說明,可以根據需求自行 查看參數進行使用。