在.Net 4中,Task.Factory.StartNew
是啟動一個新Task
的首選方法。它有很多重載方法,使它在具體使用當中可以非常靈活,通過設置可選參數,可以傳遞任意狀態,取消任務繼續執行,甚至控制任務的調度行為。所有這些能力也帶來了復雜性的提升,你必須知道何時應該使用何種重載方法,提供哪種調度方式等等。並且Task.Factory.StartNew
這種寫法也不夠簡潔明快,至少對它使用的主要場景不夠快,一般它使用的主要場景只是將一個工作任務丟給一個后台線程執行而已。
於是,在.NET Framework 4.5開發者預覽版中,微軟引進了新的Task.Run
方法。新方法不是為了替代舊的Task.Factory.StartNew
方法,只是提供了一種使用Task.Factory.StartNew
方法的更簡潔的形式,而不需要去指定那一系列參數。這是一個捷徑,事實上,Task.Run
的內部實現邏輯跟Task.Factory.StartNew
一樣,只是傳遞了一些默認參數。比如當你使用Task.Run
:
Task.Run(someAction);
實際上等價於:
Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
使用這些默認參數,Task.Run
就能用於大多數情況——只是將任務簡單的交給后台線程池去執行(這也是使用TaskScheduler.Default
參數的目標)。這也並不意味着Task.Factory.StartNew
方法就不必再使用了,它仍然有很多重要的用處。你可以通過控制TaskCreationOptions
參數來控制任務的行為,你也可以通過控制TaskScheduler
來控制任務應該如何排隊如何運行,你也可以使用重載方法中的接受對象狀態那個參數,對於一些性能敏感的代碼,它可以用於避免閉包以及相應的資源分配。不過對於上面那個簡單的例子,Task.Run
是最友好。
Task.Run
提供了八種重載方式,用於提供下面這幾種組合情況:
- 普通任務(
Task
)和帶返回值任務(Task<TResult>
) - 可取消任務(
Cancelable
)和不可取消任務(non-cancelabl
) - 同步委托(
Synchronous
)和異步委托(Asynchronous
)
前兩個很明顯,對於第一點如果是用的Task
做返回值的重載方法,那么該任務就沒有返回值,如果是用的Task<TResult>
做返回值的重載方法,那么該任務就有一個類型為TResult
的返回值。對於第二點,也有接受CancellationToken
參數的重載,可以在任務開始之前執行取消操作,然后並行任務(Task Parallel Library——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>>
。這有什么關系呢?考慮下如果我們現在使用下面這種寫法:
var t = Task.Factory.StartNew(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
的重載方法中有可以接受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/anonymous
方法或是異步lambdas/anonymous
方法,最后總會按你所期望的行為運行。如果我們想讓任務在后台運行並且想等待它的結果,那么可以像下面這樣寫:
int result = await Task.Run(async () =>
{
await Task.Delay(1000);
return 42;
});
此處變量result
的類型正是你所期望的int
,並且在該任務被調用大約1秒鍾后,變量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>>
,對Task<Task<int>>
使用await
實際上返回Task<int>
,然后再對Task<int>
使用await
最后返回int
,難道不是這樣嗎?
原文鏈接,2011年10月24日發布:
http://blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx