【问题标题】:Awaiting tasks from multiple objects等待来自多个对象的任务
【发布时间】:2019-02-07 02:54:39
【问题描述】:

我有一个使用 MEF 加载插件的应用程序。所有这些插件都符合以下接口:

public interface IPlugin {
    Task Start();
}

所有方法都实现为async:public async Task Start()

当应用程序运行时,我有一个可用于所有插件的IEnumerable<IPlugin> 属性。问题基本上是如何并行运行所有Start() 方法并等到所有方法完成?

我知道Parallel.ForEach(plugins, plugin => plugin.Start()),但这不是等待的,在所有插件启动之前继续执行。

最有希望的解决方案似乎是Task.WhenAll(),但我不知道如何在不添加一些脚手架的情况下将未知的方法列表发送到其中(这似乎是开销)。

我怎样才能做到这一点?

【问题讨论】:

    标签: c# async-await .net-core task task-parallel-library


    【解决方案1】:

    这里有一个单行:

    await Task.WhenAll(plugins.Select(p => p.Start()));
    

    插件将异步运行,但不会并行运行。如果您出于某种原因想明确地将插件分派到线程池,您可以将 Task.Runasync lambda 添加到 Select 中。

    【讨论】:

    • 板凳之子,它一直盯着我的脸!您是否愿意详细说明“插件将异步运行,但不会并行运行”?
    • 你可以输出CurrentThread的id,你会看到插件至少在一个线程上启动。这是一篇关于它的好文章:blogs.msdn.microsoft.com/benwilli/2015/09/10/…
    • @Sergey.quixoticaxis.Ivanov 对于并行运行,使用AsParallel()(如plugins.AsParallel().Select(p => p.Start()))而不是Task.Run 会不会更好?见here
    • @pere57 我怀疑它会有所作为。此外,我不确定插件是否可以在并行查询中从工作“分块”中获得任何价值。应该测量irl,这种情况下哪个更好。
    【解决方案2】:

    你可以这样做:

    var tasks = new List<Task>();
    foreach(var plugin in plugins) 
    {
       var task = plugin.Start();
       tasks.Add(task);
    }
    await Task.WhenAll(tasks); 
    

    【讨论】:

    • 我最讨厌的是if(collection.Count &gt; 0) - 使用if(collection.Any())。您不在乎有多少物品,只要“有吗?”。
    • @Neil,非常好的建议!我总是忘记Any() 在这种情况下效率更高。 :)
    • 或者你可以省略Any检查,因为WhenAll可以处理零任务。
    • 你的意思可能是Task.WhenAll,而不是Tasks.WhenAll
    • @Sergey.quixoticaxis.Ivanov,我不知道您可以将空集合传递给它,所以感谢您的建议。
    【解决方案3】:

    您可以使用 Microsoft 的 Reactive Framework 来确保这是可等待的、异步并行发生的。

    await
        plugins
            .ToObservable()
            .SelectMany(plugin => Observable.FromAsync(() => plugin.Start()))
            .ToArray();
    

    【讨论】:

      【解决方案4】:

      如您所见,Start 方法返回一个Task。我会定义一个插件加载任务列表,并在每个任务完成时检查Task.WhenAll。之后,您可以假设所有 Start 方法都已返回。

      List<IPlugin> plugins = ... 
      var pluginsLoadingTasks = new List<Task>();
      
      foreach(var plugin in plugins)
      {
          pluginsLoadingTasks.Add(plugin.Start());
      }
      
      // It's not necessary to check if pluginsLoadingTasks is empty, 
      // because WhenAll won't throw an exception in that case
      await Task.WhenAll(pluginsLoadingTasks);
      // You can assume all Start methods have completed
      

      我建议您在 Task.WhenAllParallel.ForEach 构造之间使用 to read the differences

      【讨论】:

      • 你的回答与弗兰克的建议有何不同?
      猜你喜欢
      • 2021-09-28
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 2019-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多