【问题标题】:List of objects with async Task methods, execute all concurrently具有异步任务方法的对象列表,所有并发执行
【发布时间】:2012-12-04 07:37:02
【问题描述】:

鉴于以下情况:

BlockingCollection<MyObject> collection;

public class MyObject
{
   public async Task<ReturnObject> DoWork() 
   {       
      (...) 
      return await SomeIOWorkAsync();
   }
}

collection 中的 all MyObjects 上异步执行 all DoWork() 任务的正确/最高效的方法是什么(同时捕获返回对象),理想情况下有一个合理的线程限制(我相信任务工厂/线程池在这里做了一些管理)?

【问题讨论】:

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


    【解决方案1】:

    您可以使用 WhenAll 扩展方法。

    var combinedTask = await Task.WhenAll(collection.Select(x => x.DoWork());
    

    它将同时启动所有任务并等待所有任务完成。

    【讨论】:

    • 如果我想立即捕获每个单独的 DoWork() 返回而不是等待所有项目完成,是否有不同的方法?
    • collection.Select(x =&gt; x.DoWork()) 返回一个IEnumerable&lt;Task&gt;,因此您可以执行正常的Task 操作,如ContinueWith
    【解决方案2】:

    ThreadPool 管理运行的线程数,但这对异步Tasks 没有多大帮助。

    因此,您需要其他东西。一种方法是利用 TPL Dataflow 中的ActionBlock

    int limit = …;
    IEnumerable<MyObject> collection = …;
    
    var block = new ActionBlock<MyObject>(
        o => o.DoWork(),
        new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = limit });
    
    foreach (var obj in collection)
        block.Post(o);
    
    block.Complete();
    await block.Completion;
    

    【讨论】:

      【解决方案3】:

      在集合中的所有 MyObjects 上同时(同时捕获返回对象)异步执行所有 DoWork() 任务的正确/最高性能的方法是什么,理想情况下具有合理的线程限制

      最简单的方法是使用Task.WhenAll

      ReturnObject[] results = await Task.WhenAll(collection.Select(x => x.DoWork()));
      

      这将对集合中的所有MyObjects 调用DoWork,然后等待它们全部完成。线程池明智地处理所有限制。

      如果我想立即捕获每个单独的 DoWork() 返回而不是等待所有项目完成,是否有不同的方法?

      是的,您可以使用Jon SkeetStephen Toub 描述的方法。我的 AsyncEx 库中有一个 similar solution(可通过 NuGet 获得),您可以像这样使用它:

      // "tasks" is of type "Task<ReturnObject>[]"
      var tasks = collection.Select(x => x.DoWork()).OrderByCompletion();
      foreach (var task in tasks)
      {
        var result = await task;
        ...
      }
      

      【讨论】:

      • 我将“合理的线程限制”理解为“对并行度的合理限制”,而一个简单的Select() 是不够的。
      • 我理解为“即使集合很大也不会消耗大量资源”。如果操作确实想指定并行度,那么您的答案会更好(出于这个原因,我对其进行了 +1)。如果操作不需要指定并行度,我的选项是一个替代选项。
      【解决方案4】:

      我的评论有点神秘,所以我想添加这个答案:

      List<Task<ReturnObject>> workTasks =
        collection.Select( o => o.DoWork() ).ToList();
      
      List<Task> resultTasks =
        workTasks.Select( o => o.ContinueWith( t =>
            {
              ReturnObject r = t.Result;
              // do something with the result
            },
            // if you want to run this on the UI thread
            TaskScheduler.FromCurrentSynchronizationContext()
          )
        )
        .ToList();
      
      await Task.WhenAll( resultTasks );
      

      【讨论】:

        猜你喜欢
        • 2019-05-07
        • 2014-04-02
        • 1970-01-01
        • 2018-08-18
        • 1970-01-01
        • 2014-06-12
        • 1970-01-01
        • 1970-01-01
        • 2016-01-15
        相关资源
        最近更新 更多