翻譯自 Stephen Toub 2011年10月24日的博文《Task.Run vs Task.Factory.StartNew》,Stephen Toub 是微軟並行計算平臺團隊的首席架構師。閉包
在 .NET 4 中,Task.Factory.StartNew
是安排新任務的首選方法。它有許多重載提供了高度可配置的機制,經過啓用設置選項,能夠傳遞任意狀態、啓用取消,甚至控制調度行爲。全部這些功能的另外一面是複雜性。您須要知道何時使用哪一個重載、提供什麼調度程序等等。另外,Task.Factory.StartNew
用起來並不直截乾脆,至少對於它的一些使用場景來講還不夠快,好比它的主要使用場景——輕鬆地將工做交付到後臺處理線程。架構
所以,在 .NET Framework 4.5 開發者預覽版 中,咱們引入了新的 Task.Run
方法。這決不是要淘汰 Task.Factory.StartNew
,而是應該簡單地認爲這是使用 Task.Factory.StartNew
而沒必要傳遞一堆參數的一個便捷方式。這是一個捷徑。事實上,Task.Run
實際是按照與 Task.Factory.StartNew
相同的邏輯實現的,只是傳入了一些默認的參數。當你傳遞一個 Action
給 Task.Run
:app
Task.Run(someAction);
徹底等同於:異步
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
經過這種方式,Task.Run
就能夠而且應該被用於大多數通用場景——簡單地將工做交給線程池ThreadPool
處理(即參數 TaskScheduler.Default 的目標)。這並不意味着 Task.Factory.StartNew
將再也不被使用; 遠非如此,Task.Factory.StartNew
還有不少重要的(當然更高級)用途。你能夠控制 TaskCreationOptions
來控制任務的行爲,能夠控制 TaskScheduler
來控制任務的調度和運行,也可使用接收對象狀態的重載,對於性能敏感的代碼路徑,使用該重載能夠避免閉包和相應的內存分配。不過,對於簡單的狀況,Task.Run
是你的朋友。async
Task.Run
提供八個重載,以支持下面的全部組合:性能
Task
)和有返回值任務(Task<TResult>
)cancelable
)和不支持取消(non-cancelable
)synchronous delegate
)和異步委託(asynchronous delegate
)前兩點應該是不言而喻的。對於第一點,有返回 Task
的重載(對於沒有返回值的操做),還有返回 Task<TResult>
的重載(對於返回值類型爲 TResult
的操做)。對於第二點,還有接受 CancellationToken
的重載,若是在任務開始執行以前請求取消,則任務並行庫(TPL)能夠將任務轉換爲取消狀態。線程
第三點是更有趣的,它與 Visual Studio 11 中 C# 和 Visual Basic 的異步語言支持直接相關。 讓咱們暫時考慮一下 Task.Factory.StartNew
,這將有助於突出這一區別。若是我編寫下面的調用:翻譯
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>>
。 爲何這是相關的? 如今考慮一下,若是我編寫以下的代碼會發生什麼:code
var t = Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; });
這裏經過使用 async
關鍵詞,編譯器會將這個委託(delegate
)映射成 Func<Task<int>>
,調用該委託會返回 Task<int>
表示此調用的最終完成。由於委託是 Func<Task<int>>
,TResult
是 Task<int>
,所以這裏 「t」 的類型將會是 Task<Task<int>>
,而不是 Task<int>
。
爲了處理這類狀況,在 .NET 4 咱們引入了 Unwrap
方法。Unwrap
方法有兩個重載,都是擴展方法,一個針對類型 Task<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
。由於咱們但願人們將工做轉移到線程池(ThreadPool
)中並使用 async/await
成爲廣泛現象,因此咱們決定將此解包(unwrapping
)功能構建到 Task.Run
中。這就是上面第三點中提到的內容。有多種 Task.Run
的重載,它們接受 Action
(針對無返回值任務)、 Func<TResult>
(針對返回 TResult
的任務)、Func<Task>
(針對無返回值的異步任務) 和 Func<Task<TResult>>
(針對返回 TResult
的異步任務)。在內部,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/匿名方法或與異步lambdas/匿名方法一塊兒使用,都會發生正確的事情。若是我想將工做交給線程池(ThreadPool
)並等待其結果,例如:
int result = await Task.Run(async () => { await Task.Delay(1000); return 42; });
變量 result
的類型將會是 int
,正如您指望的那樣,在調用此任務大約一秒種後,變量 result
的值將被設置爲 42。
有趣的是,幾乎能夠將新的 await
關鍵字看做是與 Unwrap
方法等效的語言。所以,若是咱們返回到 Task.Factory.StartNew
示例,則可使用 Unwrap
重寫上面最後一個代碼片段,以下:
int result = await Task.Factory.StartNew(async delegate { await Task.Delay(1000); return 42; }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
或者,我可使用第二個 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>>
。 await
Task<Task<int>>
返回 Task<int>
,而後 await
Task<int>
返回 int
,頗有趣,對吧?