首頁>技術>

開啟新的模式

從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)

。。。 。。。

34
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 【200927】配置Python的Selenium環境