【问题标题】:Combine the result of two parallel tasks in one list将两个并行任务的结果合并到一个列表中
【发布时间】:2014-12-01 12:39:21
【问题描述】:

我想将 2 个任务的结果合并到一个 List 集合中。

确保 - 我想并行运行这两种方法。

代码:

List<Employee> totalEmployees = new List<Employee>();

方法一:

public async Task<IEnumerable<Employee>> SearchEmployeeFromDb();

方法二:

public async Task<IEnumerable<Employee>> GetEmployeeFromService();

现在,我想将这两个方法的结果保存在totalEmployees 字段中,这两个方法也应该异步运行。

【问题讨论】:

    标签: c# .net multithreading parallel-processing task-parallel-library


    【解决方案1】:

    虽然许多答案都很接近,但最简洁和最有效的选择是使用 Task.WhenAll 结合 SelectMany

    async Task<IEnumerable<Employee>> Combine()
    {
        var results = await Task.WhenAll(SearchEmployeeFromDb(), GetEmployeeFromService());
        return results.SelectMany(result => result);
    }
    

    这假设并行是指并发。如果您希望从一开始就使用多个线程运行这些操作(包括async 方法的同步部分),您还需要使用Task.Run 将工作卸载到ThreadPool 线程:

    private async Task<IEnumerable<Employee>> Combine()
    {
        var results =
            await Task.WhenAll(Task.Run(() => SearchEmployeeFromDb()), Task.Run(() => GetEmployeeFromService()));
        return results.SelectMany(result => result);
    }
    

    【讨论】:

    • 为什么不在WhenAll返回的任务中加入ConfigureAwait(false)?
    • @Cybrosys 为什么要添加? (除了大多数图书馆等级代码可能应该有的事实)
    • 此代码可能在库中或从另一层异步调用。除非您有特殊需要返回原始线程,否则始终使用 ConfigureAwait(false) 不是最佳做法吗?
    • @Cybrosys 这是所有async 开发的意见(可能是真的),但我认为这与这个特定问题无关。要么将其添加到所有 async 调用中(包括这个),要么不添加。
    【解决方案2】:
    • 启动两个任务
    • 使用Task.WhenAll 等待两个任务完成
    • 使用Enumerable.Concat 合并结果


    var searchEmployeesTask = SearchEmployeeFromDb();
    var getEmployeesTask = GetEmployeeFromService();
    
    await Task.WhenAll(searchEmployeesTask, getEmployeesTask);
    
    var totalEmployees = searchEmployeesTask.Result.Concat(getEmployeesTask.Result);
    

    【讨论】:

      【解决方案3】:

      您可以使用Task.WhenAll 创建一个任务,该任务将在所有提供的任务完成后返回

      var result = await Task.WhenAll(SearchEmployeeFromDb(),GetEmployeeFromService());
      var combined = result[0].Concat(result[1]);
      

      【讨论】:

      • 我还需要结合@Jamiec这两种方法的结果!!
      • Task.WhenAll 返回一个没有索引器来访问集合的 Task 对象。
      • @nunu - 你说得对,应该是 tasks.Result[x] 更新
      • @Jamiec 当您等待Task.WhenAll 的结果时,您没有得到任何任务。
      • @I3arnon - 我在这个答案上做得不是很好,是吗?!是现在(等待让你得到任务的结果,对吧?)
      【解决方案4】:

      这样的事情应该可以工作:

      var t1 = SearchEmployeeFromDb()
      var t2 = GetEmployeeFromService()
      
      await Task.WhenAll(t1, t2)
      
      // Now use t1.Result and t2.Result to get `totalEmployees`
      

      【讨论】:

        【解决方案5】:

        使用 ConfigureAwait(false) 避免死锁,定义任务,执行然后等待。

        var fromDbTask = SearchEmployeeFromDb().ConfigureAwait(false);
        var fromServiceTask = GetEmployeeFromService().ConfigureAwait(false);
        
        var fromDbResult = await fromDbTask;
        var totalEmployees = new List(fromDbResult);
        
        var fromServiceResult = await fromServiceResult;
        totalEmployees.AddRange(fromServiceResult);
        

        ...或使用您想要合并两个列表的任何方式。

        我更新了解决方案,无需创建列表然后附加第一个结果。我们等待第一个方法完成,然后创建列表。

        【讨论】:

        • 一次等待一个任务并不是真正的异步!我希望这两种方法都应该并行运行。
        • 它们都在这里执行: var fromDbTask = SearchEmployeeFromDb().ConfigureAwait(false); var fromServiceTask = GetEmployeeFromService().ConfigureAwait(false);所以是的,它们确实是异步运行的。
        • 同意,如果他们都在那里执行,那么为什么需要在将它分配给字段时使用'await'语句?
        • await 关键字只是表示如果到达这一点后任务还没有完成,我们就等待结果。这两个中哪一个先完成并不重要,因为此方法完成所需的总时间是相同的。
        • var xTask = Method().ConfigureAwait(false) 立即返回将等待任务分配给 xTask。该方法在后台执行,直到我们到达“等待”。如果任务已经完成,那么你会得到结果,如果没有,它会等到任务完成。 ConfigureAwait(false) 指定一旦等待返回它就不必在调用线程上返回,这是在“UI”不直接访问的库/api 中编写/使用异步代码时的“最佳实践”因为你避免了死锁条件。
        猜你喜欢
        • 2022-09-28
        • 2017-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-16
        • 2018-04-04
        • 1970-01-01
        • 2019-07-01
        相关资源
        最近更新 更多