一、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));
}
運行效果圖:
