Task.Run 下是否需要 await / async 关键字?

lit*_*_05 5 c# task async-await

我有一个包裹在 Task.Run 下的异步 lambda 表达式。但是,看起来我可以删除 async 和 await 关键字,它们会产生相同的结果。

t1 = Task.Run(() => DoSomethingExpensiveAsync());
t2 = Task.Run(() => DoSomethingExpensiveAsync());
await Task.WhenAll(t1, t2);
Run Code Online (Sandbox Code Playgroud)

对比

var t1 = Task.Run(async () => await DoSomethingExpensiveAsync());
var t2 = Task.Run(async () => await DoSomethingExpensiveAsync());
await Task.WhenAll(t1, t2);
Run Code Online (Sandbox Code Playgroud)
  1. 为什么编译器让我这样做以及幕后发生了什么?
  2. 是否存在添加它们会产生影响的情况?

Joh*_* Wu 5

实际上有三种变体。

var task = Task.Run(() => DoSomethingExpensiveAsync());
Run Code Online (Sandbox Code Playgroud)

^ 这个函数声明了一个新的匿名非异步函数,该函数调用DoSomethingExpensiveAsync()并返回其Task. 编译器编译这个匿名函数并将其作为参数传递给Task.Run().

var task = Task.Run( async () => await DoSomethingExpensiveAsync() );
Run Code Online (Sandbox Code Playgroud)

^ 这声明了一个新的匿名异步函数,该函数调用DoSomethingExpensiveAsync(). 然后它返回一个 incomplete Task,等待 DoSomethingExpensiveAsync()完成,然后发出任务完成的信号。

var task = Task.Run(DoSomethingExpensiveAsync);
Run Code Online (Sandbox Code Playgroud)

^ 这个函数根本没有声明一个新的匿名函数。对的直接引用DoSomethingExpensiveAsync将作为参数传递给Task.Run()

所有这些都是有效的,因为所有三个版本都返回 a ,因此与接受 aTask的重载相匹配。Task.Run()Func<Task>

作为一个黑匣子,所有三个调用最终都会做同样的事情。然而,前两个结果导致编译一个新函数(尽管我不确定它不会被优化掉),第二个结果还导致为其创建另一个状态机。

如果我们在不使用 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);
Run Code Online (Sandbox Code Playgroud)

两者之间的区别在于,一个“省略”任务,而另一个则不“省略”任务。Stephen Cleary 就该主题撰写了完整的博客


Ste*_*ary 1

编译器为什么让我这样做?幕后发生了什么?

Task.Run您调用的重载需要一个Func<Task>- 即 -Task返回函数。来自哪里并不重要Task;该函数只需要从某个地方返回它。

async如果您传递一个不带and的委托await,那么该委托只是调用一个Task返回函数并返回相同的值Taskasync如果您使用and传递委托await,则委托将调用 -Task返回函数并await保存它;Task从委托返回的实际值是async关键字创建的。

在这种情况下,两者在语义上是等效的。使用async/await关键字效率稍低,因为编译器为async委托创建了一个状态机。

是否存在添加它们会产生影响的情况?

是的。一般情况下,您应该保留asyncawait。仅在像这里这样的极其简单的“直通”情况下删除它们。