Task是可能有延遲的工作單元,目的是生成一個結果值,或產生想要的效果。任務和線程的區別是:任務代表需要執行的作業,而線程代表做這個作業的工作者。
在.Net 4中,Task.Factory.StartNew是啟動一個新Task的首選方法。一般它使用的主要場景只是將一個工作任務丟給一個后台線程執行而已。為了高度可配置機制它提供了很多重載方法,通過設置可選參數,可以傳遞任意狀態,取消任務繼續執行,甚至控制任務的調度行為。所有這些能力也帶來了復雜性的提升,你必須知道何時應該使用何種重載方法,提供哪種調度方式等等。
例如:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
使用這些默認參數,Task.Run就能用於大多數情況——只是將任務簡單的交給后台線程池去執行。當然你可以通過控制TaskCreationOptions參數來控制任務的行為,你也可以通過控制TaskScheduler來控制任務應該如何排隊如何運行,你也可以使用重載方法中的接受對象狀態那個參數,對於一些性能敏感的代碼,它可以用於避免閉包以及相應的資源分配。
Task.Factory.StartNew這種寫法不夠簡潔明快,於是,在.NET Framework 4.5及以后的版本中,微軟引進了新的Task.Run方法。新方法不是為了替代舊的Task.Factory.StartNew方法,只是提供了一種使用Task.Factory.StartNew方法的更簡潔的形式,而不需要去指定那一系列參數。這是一個捷徑,事實上,Task.Run的內部實現邏輯跟Task.Factory.StartNew一樣,只是傳遞了一些默認參數。
Task.Run(someAction)提供了以下八種重載方式,用於提供下面這幾種組合情況:
public static Task Run(Func<Task> function); /* 參數: function(以異步方式執行的工作量)、cancellationToken(應用以取消工作的取消標記) 返回結果:表示在線程池執行的隊列的任務 (TResult) */ public static Task Run(Func<Task> function, CancellationToken cancellationToken); public static Task Run(Action action); /* 參數: action(以異步方式執行的工作量)、cancellationToken(應用以取消工作的取消標記) 返回結果:表示在線程池執行的隊列的任務 */ public static Task Run(Action action, CancellationToken cancellationToken); public static Task<TResult> Run<TResult>(Func<TResult> function); /* 參數: function(以異步方式執行的工作量)、cancellationToken(應用以取消工作的取消標記) 類型參數: TResult(任務的結果類型) 返回結果:表示在線程池執行的隊列的任務 (TResult) */ public static Task<TResult> Run<TResult>(Func<TResult> function, CancellationToken cancellationToken); public static Task<TResult> Run<TResult>(Func<Task<TResult>> function); /* 參數: function(以異步方式執行的工作量)、cancellationToken(應用以取消工作的取消標記) 類型參數: TResult(代理 Task 返回的結果的類型) 返回結果:表示由 function 返回的任務 (TResult) 的代理的任務 (TResult) */ public static Task<TResult> Run<TResult>(Func<Task<TResult>> function, CancellationToken cancellationToken);
實例解釋:
var t = Task.Factory.StartNew(() => { Task inner = Task.Factory.StartNew(() => { }); return inner; });
這里t的類型會被推斷為Task<Task>,因為此處任務的委托類型是Func<TResult>,所以這里TResult的類型就是Task,於是StartNew方法就返回Task<Task>,類似的,我可以改變成下面這種寫法:
var t = Task.Factory.StartNew(() => { Task<int> inner = Task.Factory.StartNew(() => 42); return inner; });
此處的t的類型自然是Task<Task<int>>,任務的委托類型還是Func<TResult>,TResult的類型就是Task<int>,StartNew方法就返回Task<Task<int>>。這有什么關系呢?考慮下如果我們現在使用下面這種寫法:
var t = Task.Run(async delegate { await Task.Delay(1000); return 42; });
這里使用了async關鍵詞,編譯器會將這個委托映射成Func<Task<int>>,調用這個委托最終會返回Task<int>。因為這個這個委托是Func<Task<int>>,TResult的類型就是Task<int>,所以最后t的類型應該是Task<Task<int>>,而不是Task<int>。
在.Net 4中引入了Unwrap方法。Unwrap方法有兩種重載形式,均是擴展方法的形式,一種是針對類型Task<Task>,另一種是針對<Task<TResult>>。微軟只所以要把這個方法命名為解包(Unwrap),是因為這個方法可以返回任務的實際結果。對Task<Task>調用Unwrap方法可以返回一個新的Task(就像內部任務的一個代理一樣)代表它的內部任務。相似的,對Task<Task<TResult>>調用Unwrap返回一個新的Task<TResult>代表它的內部任務。但是,如果外部任務失敗了或者取消了,就不會有內部任務了,因為沒有任務運行完成,所以代理任務也就變成了外部任務的狀態。回到前面的例子,如果想讓t代表內部任務的返回值(在這個例子中,這個值是42),那么應該像下面這樣寫:
var t = Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }).Unwrap();
現在,變量t的類型是Task<int>,代表異步調用的結果。
因為微軟想讓開發者盡可能的使用Task.Run這種新的形式來啟用后台任務,並且可以配合async/await使用,所以微軟決定在Task.Run方法中內建unwrapping的功能。總的來說,Task.Run方法提供了上面Task.Factory.StartNew方法相同的unwrapping操作。於是,我們可以這樣寫:
var t = Task.Run(async delegate { await Task.Delay(1000); return 42; });
t的類型是Task<int>,此處Task.Run執行的重載方法等價於:
var t = Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
上面講的全部類容都意味着你可以使用Task.Run調用標准的lambdas/anonymous方法或是異步lambdas/anonymous方法,最后總會按你所期望的行為運行。如果我們想讓任務在后台運行並且想等待它的結果,那么可以像下面這樣寫(例1):
public async Task GetResult() { int result = await Task.Run(async () => { await Task.Delay(1000); return 42; }); }
此處變量result的類型正是你所期望的int,並且在該任務被調用大約1秒鍾后,變量result的值被設置為42。新的await關鍵字被認為是等價於Unwrap方法的一種新語法形式。
int result = await Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
這里用Unwrap重寫例1那個代碼片段,可以像下面使用第二個await替換Unwrap:
int result = await await Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
這里的await await雖然看着別扭,但是並沒有問題。Task.Factory.StartNew方法返回一個Task<Task<int>>,對Task<Task<int>>使用await實際上返回Task<int>,然后再對Task<int>使用await最后返回int。