[英]Task.Run( () MethodName()) and await Task.Run(async () => MethodName())
[英]Are the await / async keywords needed under Task.Run?
我有一個包裹在 Task.Run 下的異步 lambda 表達式。 但是,看起來我可以刪除 async 和 await 關鍵字,它們會產生相同的結果。
t1 = Task.Run(() => DoSomethingExpensiveAsync());
t2 = Task.Run(() => DoSomethingExpensiveAsync());
await Task.WhenAll(t1, t2);
對比
var t1 = Task.Run(async () => await DoSomethingExpensiveAsync());
var t2 = Task.Run(async () => await DoSomethingExpensiveAsync());
await Task.WhenAll(t1, t2);
實際上有三種變體。
var task = Task.Run(() => DoSomethingExpensiveAsync());
^ 這個函數聲明了一個新的匿名非異步函數,它調用DoSomethingExpensiveAsync()
並返回它的Task
。 編譯器編譯這個匿名函數並將其作為參數傳遞給Task.Run()
。
var task = Task.Run( async () => await DoSomethingExpensiveAsync() );
^ 這個函數聲明了一個新的匿名異步函數,它調用DoSomethingExpensiveAsync()
。 然后它返回一個未完成的Task
,等待DoSomethingExpensiveAsync()
完成,然后發出任務完成的信號。
var task = Task.Run(DoSomethingExpensiveAsync);
^ 這個根本沒有聲明一個新的匿名函數。 對DoSomethingExpensiveAsync
的直接引用將作為參數傳遞給Task.Run()
。
所有這些都是有效的,因為所有三個版本都返回一個Task
並因此匹配接受Func<Task>
的Task.Run()
的重載。
作為一個黑匣子,所有三個調用最終都會做同樣的事情。 然而,前兩個導致編譯一個新函數(盡管我不確定它不會被優化掉),而第二個也會導致為它創建另一個狀態機。
如果我們在不使用 lambda 表達式或匿名函數的情況下重寫它們,差異可能會更清楚。 以下代碼完全相同:
//This is the same as Task.Run( () => DoSomethingExpensiveAsync());
Task Foo()
{
return DoSomethingExpensiveAsync();
}
var task = Task.Run(Foo);
//This is the same as Task.Run(async () => await DoSomethingExpensiveAsync());
async Task Bar()
{
return await DoSomethingExpensiveAsync();
}
var task = Task.Run(Bar);
這兩者之間的區別在於,一個“省略”了任務,而另一個則沒有。 Stephen Cleary 寫了一篇關於這個主題的完整博客。
為什么編譯器讓我這樣做以及幕后發生了什么?
您正在調用的Task.Run
的重載需要一個Func<Task>
- 即一個Task
返回函數。 Task
來自哪里並不重要; 該函數只需要從某個地方返回它。
如果您在沒有async
和await
情況下傳遞委托,則該委托只是調用Task
返回函數並返回相同的Task
。 如果您使用async
和await
傳遞委托,則委托會調用Task
返回函數並await
它; 從委托返回的實際Task
是由async
關鍵字創建的。
在這種情況下,兩者在語義上是等價的。 使用async
/ await
關鍵字效率稍低,因為編譯器為async
委托創建了一個狀態機。
是否存在添加它們會產生影響的情況?
是的。 在一般情況下,您應該保持async
和await
。 只有在非常簡單的“直通”情況下才能刪除它們,比如這里的情況。
你的代碼是一樣的
t1 = DoSomethingExpensiveAsync();
t2 = DoSomethingExpensiveAsync();
await Task.WhenAll( t1, t2 );
因為Task.Run( Func< function )將返回由函數生成的任務的代理。 沒有創建其他任務,因此您正在等待原始任務。
如果您已經有了異步方法,則根本不需要使用Task.Run
。
如果不知道DoSomethingExpensiveAsync
是什么,就無法確定會發生什么。
讓我們假設DoSomethingExpensiveAsync
是這樣的:
async Task DoSomethingExpensiveAsync()
{
SynchronousMethod();
await AsynchronousMethod();
}
在第一個片段中, Task.Run
會將DoSomethingExpensiveAsync
的調用調度到線程池,並在SynchronousMethod
返回后立即返回。
在第二個代碼段中, Task.Run
會將DoSomethingExpensiveAsync
的調用調度到線程池,並在DoSomethingExpensiveAsync
的Task
返回完成時返回。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.