【问题标题】:Avoiding await in foreach loop避免在 foreach 循环中等待
【发布时间】:2018-06-17 05:21:06
【问题描述】:

我正在尝试优化此代码以减少完成forloop 所需的时间。在这种情况下,CreateNotification() 需要很长时间,并且使用 async await 不会提高性能,因为正在等待每个异步调用。我想使用Task.WhenAll() 来优化代码。我怎样才能做到这一点?

foreach (var notification in notificationsInput.Notifications)
{
  try
  {
    var result = await CreateNotification(notification);
    notification.Result = result;          
  }
  catch (Exception exception)
  {
    notification.Result = null;
  }
  notifications.Add(notification);
}

【问题讨论】:

  • 你的 CreatNotification 做什么以及context是什么
  • 你可以改用Parallel.ForEach
  • CreateNotification 是一个 lambda 函数。 CreateNotification 调用端点来创建通知。我已经编辑了代码以删除上下文
  • @Leandro 这与异步方法的配合不太好:)

标签: c# .net performance async-await


【解决方案1】:

您可以在要并行处理其元素的集合上调用Select,将异步委托传递给它。这个异步委托将为每个处理的元素返回一个Task,因此您可以在所有这些任务上调用Task.WhenAll。模式是这样的:

var tasks = collection.Select(async (x) => await ProcessAsync(x));
await Task.WhenAll(tasks);

你的例子:

var tasks = notificationsInput.Notifications.Select(async (notification) =>
{
    try
    {
        var result = await CreateNotification(notification);
        notification.Result = result;          
    }
    catch (Exception exception)
    {
        notification.Result = null;
    }
});
await Task.WhenAll(tasks);

这假定CreateNotification 是线程安全的。

【讨论】:

  • 这是否并行执行?我认为这仍然是异步执行的。
  • @DavidB:异步任务将并行执行。在await 关键字处,控制权将返回给调用者;因此,Select 将继续迭代其余通知,而之前的 CreateNotification 任务仍在进行中。
【解决方案2】:

更新

您需要安装 DataFlow 才能使用此解决方案

https://www.nuget.org/packages/System.Threading.Tasks.Dataflow/


取决于CreateNotification 是什么以及您是否要并行运行它。

您可以使用 DataFlow ActionBlock,如果这是 IO 限制 或混合 IO/CPU 限制 操作,它会给您两全其美,让您并行运行async

public static async Task DoWorkLoads(NotificationsInput notificationsInput)
{
   var options = new ExecutionDataflowBlockOptions
                     {
                        MaxDegreeOfParallelism = 50
                     };

   var block = new ActionBlock<Something>(MyMethodAsync, options);

   foreach (var notification in notificationsInput.Notifications)
      block.Post(notification);

   block.Complete();
   await block.Completion;

}

...

public async Task MyMethodAsync(Notification notification)
{       
     var result = await CreateNotification(notification);
     notification.Result = result;    
}

加入胡椒和盐调味。

【讨论】:

    【解决方案3】:

    我认为这应该等同于您的代码:

    var notifications = new ConcurrentBag<Notification>();
    var tasks = new List<Task>();
    foreach (var notification in notificationsInput.Notifications)
    {
        var task = CreateNotification(notification)
                        .ContinueWith(t =>
                        {
                            if (t.Exception != null)
                            {
                                notification.Result = null;
                            }
                            else
                            {
                                notification.Result = t.Result;
                            }
                            notifications.Add(notification);
                        });
        tasks.Add(task);
    }
    await Task.WhenAll(tasks);
    

    .ContinueWith( 将从CreateNotification( 接收完成/失败的任务,并且它本身就是一个任务。我们将ContinueWith 任务添加到列表中并在WhenAll( 中使用它。

    我使用ConcurrentBag 通知通知,以便您可以安全地从多个线程添加。如果你想把它变成一个常规列表,你可以调用var regularListNotifications = notifications.ToList();(假设你有一个用于 LINQ)。

    【讨论】:

      猜你喜欢
      • 2021-06-21
      • 2020-05-09
      • 2018-02-12
      • 1970-01-01
      • 1970-01-01
      • 2019-05-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多