【问题标题】:Difference between .ForEach(async x=> {await}) and foreach{await} in an asnyc methodasnyc 方法中 .ForEach(async x=> {await}) 和 foreach{await} 之间的区别
【发布时间】:2017-10-06 12:46:29
【问题描述】:

这两个简化的代码有什么区别,特别是 foreach 部分。

public async Task UploadFilesAsync(IEnumerable<UploadFile> uploads)
{
    uploads.ToList().ForEach(async upload => 
    {
     await UploadFileAsync(upload);
    });
}

public async Task UploadFilesAsync(IEnumerable<UploadFile> uploads)
{
    foreach (upload in uploads)
     {
      await UploadFileAsync(upload);
     }
}

我知道第一个本质上创建了一个 async void 操作,由于各种原因,这不是最好的解决方案,但第二个会做同样的事情,还是更被接受的做法?

【问题讨论】:

标签: c# async-await task


【解决方案1】:

由于第一个方法包含一个不属于异步执行的 foreach,它将启动内部匿名方法并退出。匿名方法将在 UploadFileAsync 方法完成后完成。

第二个版本按顺序运行,因为 foreach 等待每次迭代(在调用 MoveNext() 之前),因此 UploadFileAsync 方法将在所有内部调用完成后退出。

您可以使用以下代码对其进行测试:

    class Program
{
    static void Main(string[] args)
    {
        int[] uploads = new int[] { 600, 2000, 1000 };

        UploadFilesAsync(uploads).ConfigureAwait(true);
        UploadFilesAsync2(uploads).ConfigureAwait(true);

        Console.ReadLine();
    }

    public static async Task UploadFilesAsync(IEnumerable<int> uploads)
    {
        Console.WriteLine("Start version 1");

        uploads.ToList().ForEach(async upload =>
        {
            Console.WriteLine("Start version 1 waiting " + upload);

            await Task.Delay(upload);

            Console.WriteLine("End version 1 waiting " + upload);
        });

        Console.WriteLine("End version 1");
    }

    public static async Task UploadFilesAsync2(IEnumerable<int> uploads)
    {
        Console.WriteLine("Start version 2");

        foreach (var upload in uploads)
        {
            Console.WriteLine("Start version 2 waiting " + upload);

            await Task.Delay(upload);

            Console.WriteLine("End version 2 waiting " + upload);
        }

        Console.WriteLine("End version 2");
    }
}

所以,这不是对与错的问题。它只是关于您想要实现的目标。如果您在 UploadFilesAsync 方法中创建了资源并希望在内部匿名方法中使用它们,则使用第一个版本很容易遇到闭包问题。由于该方法在上传完成之前退出,因此您创建的资源将在内部方法使用它们之前被释放。

【讨论】:

  • The second version runs synchronosly 不,它按顺序运行。它是异步运行的,只是每个异步操作要等到前一个操作完成后才开始。
  • So, this is not a matter of right or wrong. 即使您确实只是想触发所有操作然后忘记它们,这是一种非常糟糕的方式,因为它不是显然那是你的意图。事实上,看起来他们正在尝试按顺序运行这些操作,但他们只是未能正确执行。如果他们确实想将他们全部解雇并忘记他们,他们应该编写代码以明确其意图。否则我会认为这是一个错误。
  • 感谢 cmets 服务,
【解决方案2】:

首先要做的事情。您的重点似乎是代码的 async/await 部分,所以我将首先回答。

我认为从这个角度来看根本没有任何区别

但除此之外还有一些有趣的区别

  1. 通过在方法 #1 中调用 uploads.ToList(),您正在迭代完整的 IEnumerable&lt;UploadFile&gt; uploads,而在方法 #2 中,您正在使用后期执行的力量
  2. 通过使用List&lt;T&gt;.ForEach,您需要创建一个lambda expression,其主体将为List&lt;T&gt; 中的每个元素调用。如果它被调用很多次并且做相当轻量级的工作,这可能会相当昂贵。在方法 #2 中,您显然避免了这种不必要的开销
  3. 异常处理在方法 #1 中会很麻烦,而方法 #2 允许使用典型的异常处理机制

现在,请了解,当您的代码遇到 await 调用时,它几乎会暂停以等待 Task 完成。除非您想在该调用之后处理 UploadFileAsync(upload) 的结果,否则您可以在不使用 await'ing 的情况下继续执行,然后继续执行,直到需要这些结果为止

public async Task UploadFilesAsync(IEnumerable<UploadFile> uploads)
{
    foreach (upload in uploads)
    {
        UploadFileAsync(upload);
    }
}

这样,您可以让调用者决定何时等待结果。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-16
    • 2018-07-09
    • 1970-01-01
    • 1970-01-01
    • 2016-07-11
    • 2021-06-20
    • 1970-01-01
    • 2021-09-20
    相关资源
    最近更新 更多