要么Visual Studio感到困惑,要么我(可能是我(。
如果我有
public async Task<Response> DoSomething()
{
//stuff
listOfStuff.ForEach(async s => await _repo.DoThing(s));
return new Response(listOfStuff.Count);
}
它抱怨说
这种异步方法缺少"等待"...
但是,如果我将方法更改为
public async Task<Response> DoSomething()
{
//stuff
foreach (var s in listOfStuff)
{
await _repo.DoThing(s);
}
return new Response(listOfStuff.Count);
}
然后它非常高兴,警告消失了。
所以双重问题,警告是否正确,如果是,差异的原因是什么?我的印象是这两种方法本质上是相同的,如果警告是正确的,那么我必须假设我的印象是错误的。
在第一个版本中,DoSomething
函数缺少await
运算符。唯一的await
是在传递给ForEach
方法的异步空 lambda 中。在第二个版本中,DoSomething
函数具有作为函数实际主体的一部分的await
,因此您不会收到警告。无论listOfStuff
是否为空,都满足方法需要await
的条件。
现在功能差异可能不是立即清楚的,但它是至关重要的。如前所述,第一个版本使用异步 void 方法。这是不可等待的,因此该方法将在异步操作完成之前继续,如您在此测试中看到的那样:
[Test]
public void DoSomething()
{
var sw = Stopwatch.StartNew();
var list = Enumerable.Range(0, 10).ToList();
list.ForEach(async x => await Task.Delay(TimeSpan.FromSeconds(1)));
Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
}
结果6ms
,而不是我们预期的 10 秒。代码的第二个版本是可正确等待的async Task
:
[Test]
public async Task DoSomething()
{
var sw = Stopwatch.StartNew();
var list = Enumerable.Range(0, 10).ToList();
foreach(var x in list)
{
await Task.Delay(TimeSpan.FromSeconds(1));
}
Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
}
我们看到的结果是:10032ms
在预期之内。
如果我们用本地函数替换 lambda,事情可能会变得更加清晰:
public async Task<Response> DoSomething()
{
//stuff
listOfStuff.ForEach(ProcessStuffAsync);
return new Response(listOfStuff.Count);
async Task ProcessStuffAsync(Stuff s) // local function
{
await _repo.DoThing(s);
}
}
DoSomething
方法返回一个Task
,但它并不是真正的异步,因为它缺少await
。因此,返回的Task
将处于已完成状态。DoSomething
中的所有代码(不包括本地函数中的代码(都将同步运行。
本地函数ProcessStuffAsync
是真正异步的,每次调用时都会返回一个Task
。这些任务既不等待也不存储在DoSomething
方法中的某个位置,因此它们是即发即弃的任务。没有人知道他们会发生什么。
更新:上面的代码无法编译,因为List.ForEach
无法接受ProcessStuffAsync
本地函数作为参数。编译器抱怨它的返回类型错误。要使代码编译,本地函数必须返回 void
。但是异步空函数本身就是一罐蠕虫。