在 lambda 中具有等待的异步方法中缺少等待警告



要么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 。但是异步空函数本身就是一罐蠕虫。

相关内容

最新更新