開啟新的模式
從Thread入門,到執行緒池,再到鎖和死鎖,如果你已經掌握了這些知識點,那麼進行常規的多執行緒程式設計,我相信你已經可以駕輕就熟,遊刃有餘了。
就在上次課程結束之後,小明同學向我提出了疑問:
既然有執行緒池這樣的更先進的技術,為什麼不使用執行緒池替代執行緒呢?
這個提問非常好,儘管執行緒池不能適應全部的場景,比如執行長時間執行的任務,就不宜使用執行緒池。但是微軟也在思考同樣的問題。在.Net Framework發展到4.0的時候,微軟在2010年釋出了全新的多執行緒程式設計API:Task。
簡單例項先來看一個Task的簡單例項:
class Program{ static void Main(string[] args) { Console.WriteLine("Hello Task!"); var t1 = Task.Run(Func1); var t2 = Task.Run(Func2); Task.WaitAll(t1, t2); } static void Func1() { for (int i = 0; i < 10; i++) { Console.WriteLine($"Func1 is Running {i} ......"); // System.Threading.Thread.Sleep(500); Task.Delay(500).Wait(); } } static void Func2() { for (int i = 0; i < 10; i++) { Console.WriteLine($"Func2 is Running {i} ......"); Task.Delay(333).Wait(); } }}
這個程式,其實是基於Thread入門中的例項修改的,其執行結果和Thread無異:
並行執行
從上面的簡單示例中,可以看到,透過Task開啟執行緒,更加簡單。Task.Wait方法,可以等待多個執行緒。同時也提供了Task.Delay來替代Thread.Sleep。
此外,Task還提供了以下API:
Task自帶執行緒工廠,方便隨時建立TaskTask透過CancellationToken協同取消執行緒Task提供了更完善的異常處理機制Task支援Wait WaitAny WaitAllTask支援WhenAny WhenAllTask支援ContinueWith,節省執行緒開銷Task支援Yield操作,類似協程Task透過TaskScheduler可以支援執行緒佇列Task還可以配合 async 和 await 關鍵字,更優雅的寫程式自從我習慣了使用Task之後,就很少使用Thread了。
這些API很簡單,大家自己練習。我這裡演示一下ContinueWith。我們把上面的程式碼進行修改,讓兩個函式序列執行,這個可以用來模擬兩個相互依賴的任務的執行過程:
class Program{ static void Main(string[] args) { Console.WriteLine("Hello Task!"); var t1 = Task.Run(Func1); var t2 = t1.ContinueWith(Func2); // t2.ContinueWith(Func3) ... ... t2.Wait(); } static void Func1() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Func1 is Running {i} ......"); Task.Delay(500).Wait(); } } static void Func2(Task t) { for (int i = 0; i < 5; i++) { Console.WriteLine($"Func2 is Running {i} ......"); Task.Delay(333).Wait(); } }}
執行結果:
非同步序列執行
可以看到,只用了一個Wait,可以達到Wait兩個Task的效果。而且可以很好的控制任務執行的順序。這個在執行一些有依賴關係的任務的時候,非常有用。比如我們前面提到的早餐店的流水線,模擬一碗麵的加工過程,就是序列的。使用Task來模擬這個流水線,將會更加直觀。
Task與Thread的區別API層面的區別,大家可以F1或者F12去比較。這裡我簡單說一下底層的實現機制部分不同:
Task基於ThreadPool的CLR執行緒實現,這與Thread預設託管作業系統執行緒不同
Task雖然基於ThreadPool,但是Task同時又提供了Wait這類監測執行緒執行狀態的機制,從而彌補了ThreadPool的部分侷限
Task預設為後臺執行緒,未執行完的任務,不阻止程序退出;而Thread預設為前臺任務,如果不顯式指定Thread為後臺執行緒,在Thread退出之前,程序無法退出
Task應該透過CancellationToken來協同取消,而不支援Abort這樣的粗暴取消,從而避免強制結束執行緒導致的部分資源未正常釋放的問題
Task提供LongRunning選項,來建立需要長時間執行的執行緒。從而避免執行緒池阻塞問題
Task 是 TPL(Task Parallel Library)提供一個類,可以看到,它在 Thread 和 TheadPool 之間提供了一種兩全其美的解決方案。記住,C#多執行緒程式設計,首選Task!
作業將此前的早餐店流水線,使用Task改造,體驗Task的優勢。並嘗試使用Task的其他新的API。
聰明的小明
踩坑記錄暫無
下期預告下面是給同學們準備的乾貨,正在陸續發貨中哦:
Parallel
await/async
Linq與PLinq (ParallelEnumerable)
。。。 。。。