一、Task的機制
Task位於命名空間System.Threading.Tasks中,是.NET 4.0加入的新模塊,其實現機制大致類似於線程池ThreadPool,不過對於ThreadPool來說Task的優勢是很明顯的:
ThreadPool的實現機制:(一對多)
1、應用程序擁有一個用於存放委托的全局隊列;
2、使用ThreadPool.QueueUserWorkItem將新的委托加入到全局隊列;
3、線程池中的多個線程按照先進先出的方式取出委托並執行。
Task的實現機制:(多對多)
1、應用程序擁有一個用於存放Task(包裝的委托)的全局隊列(存放主程序創建的Task,標記為了TaskCreationOptions.PreferFairness的Task),以及線程池中每個工作線程對應的本地隊列(存放該工作線程創建的Task);
2、使用new Task()或Task.Factory.StartNew將新的Task加入到指定隊列;
3、線程池中的多個線程按照優先處理本地隊列,其次處理全局隊列的方式取出Task並執行;
4、如果工作線程A發現本地隊列為空(Task已處理完畢),那么A就會嘗試去全局隊列中獲取Task,如果全局隊列也為空,那么A就會到工作線程B的本地隊列中“竊取”一個Task來執行,這種策略很明顯的使得CPU更加充分的利用了並行執行。
二、Task的使用
創建Task並運行:
//新建一個Task Task t1 = new Task(() => { Console.WriteLine("Task完成!"); }); //啟動Task t1.Start(); Console.WriteLine("UI線程完成!");
上面是用new關鍵字創建,等同於如下使用Task.Factory(Task工廠)創建的方式:
//新建一個Task(Task工廠創建,自動啟動) Task t1 = Task.Factory.StartNew(() => { Console.WriteLine("Task完成!"); }); Console.WriteLine("UI線程完成!");
這里為了簡便使用了Lambda表達式(=> 為Lambda運算符),上面兩部分代碼都等同於如下:
void Test() { Console.WriteLine("Task完成!"); } Action action = new Action(Test); //新建一個Task Task t1 = new Task(action); //啟動Task t1.Start(); Console.WriteLine("UI線程完成!");
運行效果圖:

Task的執行方式有同步和異步兩種,上面的方式很明顯是異步執行,我們可以看到做為主線程的UI線程是先一步執行完的。
那么要怎么樣才能實現Task的同步執行呢?主要就這一個方法:Wait()!
代碼如下:
//新建一個Task Task t1 = new Task(() => { Console.WriteLine("Task完成!"); }); //啟動Task t1.Start(); Console.WriteLine("UI線程開始等待!");//① t1.Wait(); Console.WriteLine("UI線程完成!");//②
運行效果圖:

主線程運行到t1.Wait()時,會讀取t1的狀態,當發現任務t1還未執行結束時,主線程便會阻塞在這個位置(只是阻塞在t1.Wait()位置,也就是說t1.Wait()之前的代碼①照舊執行),當讀取到t1的狀態為已經執行結束時,主線程才會再次恢復執行,從t1.Wait()之后的位置②繼續往下執行。
當然,當有多個任務都需要保持同步執行時,可以使用Task.WaitAll方法同時等待多個任務完成,代碼如下:
//新建一個Task Task t1 = new Task(() => { Console.WriteLine("Task1完成!"); }); //新建一個Task Task t2 = new Task(() => { Console.WriteLine("Task2完成!"); }); //啟動Task t1.Start(); t2.Start(); Console.WriteLine("UI線程開始等待!"); //等待t1,t2都完成 Task.WaitAll(t1,t2); Console.WriteLine("UI線程完成!");
運行效果圖:

當然,對於Task的操作還有更多,這里對於我的需求無關緊要,所以不再列舉,詳情請參見MSDN的API文檔:
三、基於Task的異步與延時
我在這里進行了如下封裝:
/// <summary> /// 開始一個異步任務 /// </summary> /// <param name="taskAction">異步任務執行委托</param> /// <param name="taskEndAction">異步任務執行完畢后的委托(會跳轉回UI線程)</param> /// <param name="control">UI線程的控件</param> public void StartAsyncTask(Action taskAction, Action taskEndAction, Control control) { if (control == null) { return; } Task task = new Task(() => { try { taskAction(); //返回UI線程 control.Invoke(new Action(() => { taskEndAction(); })); } catch (Exception e) { MessageBox.Show(e.Message); } }); task.Start(); } /// <summary> /// 開始一個延時任務 /// </summary> /// <param name="DelayTime">延時時長(秒)</param> /// <param name="taskEndAction">延時時間完畢之后執行的委托(會跳轉回UI線程)</param> /// <param name="control">UI線程的控件</param> public void StartDelayTask(int DelayTime, Action taskEndAction, Control control) { if (control == null) { return; } Task task = new Task(() => { try { Thread.Sleep(DelayTime * 1000); //返回UI線程 control.Invoke(new Action(() => { taskEndAction(); })); } catch (Exception e) { MessageBox.Show(e.Message); } }); task.Start(); }
StartAsyncTask主要是執行一個異步操作,並在異步操作完成后執行指定的委托,這里因為Task的執行機制依然是多線程,由於winform的線程安全性使得非UI線程無法訪問UI線程中的UI控件,所以在Task操作結束后執行的委托有必要返回到UI線程中,也就是說StartAsyncTask主要的功能就是在taskAction中執行一系列的異步運算,運算結束之后在taskEndAction中進行一些可視化的表現,比如給某某UI控件賦值。
StartDelayTask幾乎等同於StartAsyncTask,只不過他更加的表現出來一種延時的特性,事實上在StartAsyncTask的taskAction中加入線程Sleep也就是StartDelayTask的效果了。
StartAsyncTask的使用,異步耗時操作:
//顯示耗時等待界面(比如一串文字:正在加載,請稍等......) WaitPage.ShowWait(); StartAsyncTask( () => { //進行耗時操作...... }, () => { //耗時操作完成,隱藏耗時等待界面 WaitPage.HideWait(); }, this);
StartDelayTask的使用,異步延時等待:
Console.WriteLine("我軍將在三秒后發起反擊!"); StartDelayTask( //等待的秒數 3, () => { //等待結束要做的事 Console.WriteLine("我軍開始反擊!"); }, this); for (int i = 0; i < 10; i++) { Console.WriteLine(String.Format("敵軍第{0}輪進攻!",i)); }
運行效果圖:
