Task.WhenAll 不等待任务完成[关闭]

Posted

技术标签:

【中文标题】Task.WhenAll 不等待任务完成[关闭]【英文标题】:Task.WhenAll doesn't wait for the tasks to be done [closed] 【发布时间】:2021-07-17 20:34:04 【问题描述】:

我正在编写从服务器获取不同数据部分的代码,将它们存储在 dict 中,以便它们按正确的顺序排列,然后在获取所有数据后将它们组合起来。但由于某种原因,它不起作用。它在获取所有内容之前进入CombineDataParts()。我不明白我做错了什么,因为所有方法都在等待。请帮忙!

public async Task<Data> GetTableData(List<string> partNames)

    List<Task> TaskList = new List<Task>();
    SortedDictionary<string, Data> dataList = new SortedDictionary<string, Data>();
    
    foreach (var name in partNames)
    
        TaskList.Add(SaveDatainDict(name, DataList));
    
    
    await Task.WhenAll(TaskList);
    return CombineDataParts(tableDataList.Select(t => t.Value).ToList());



private async Task SaveDatainDict(string part, SortedDictionary<string, Data> dataList)

    dataList[part] = await GetDataPart(part);



private async Task<Data> GetDataPart(string key)
   
    using (var response = await client.GetData(key))
    
        return ProcessData(response.Stream);
       

【问题讨论】:

SortedDictionary&lt;K,V&gt; 类不是线程安全的。您能否尝试在SaveDatainDict 方法的开头添加lock (dataList),看看是否有什么不同? @TheodorZoulias:这是个糟糕的主意。使用SortedDictionary 需要锁定dataList[part] = (something); 操作,但您建议在await GetDataPart(part) 期间也持有锁定 老实说,我认为这不是字典线程问题。我猜所有的keys 都是独一无二的?我很好奇您是否在return CombineDataParts 行上设置了断点,如果您在TaskList 中看到正确数量的任务并且所有IsCompletedSuccessfully 的值为true。他们中的任何一个都有例外吗? 如果它线程问题,您能否通过保留GetDataPart 中的List&lt;Task&lt;Data&gt;&gt; 来消除中间数据结构和方法?然后Task.WhenAll will return an array of Data 可以在其上运行Select 语句。 @BenVoigt 是的,你是对的。只是按照我建议的方式添加 lock 可能甚至无法编译。 【参考方案1】:

为了让未来的读者更清楚,将我的评论分解为完整的答案...

正如@TheodorZoulais 所暗示的,这可能是SortedDictionary 不是线程安全的问题。这意味着当任务本身完成时,SortedDictionary 不能保证立即在线程间一致地反映状态,这在这里引起了头疼。这可能可以通过锁定正确的位置来解决……但有一种更简单的方法。

由于您只是使用SortedDictionary 来组合Tasks 的结果,我们可以通过稍微不同的使用Task.WhenAll 来完全避开问题。 Task.WhenAll 做了两件非常方便的事情:

    为我们返回一个任务到await 如果我们传递给Task.WhenAllTasks 有结果,它会将这些结果合并到一个可枚举的结果中(link to the relevant documentation)

第一点是你现在的使用方式,这很好。但是如果我们也使用第二点,我们可以完全取消 SortedDictionary。

因此,只需对您的代码进行一些调整即可让我们实现线程安全...

public async Task<Data> GetTableData(List<string> partNames)

    // This time around, we will keep a list of Task<Data> instead of just Task
    List<Task<Data>> taskList = new List<Task<Data>>();

    foreach (var name in partNames)
    
        // Directly save the tasks from GetDataPart
        taskList.Add(GetDataPart(name));
    

    // Save the results combined by Task.WhenAll
    IEnumerable<Data> dataParts = await Task.WhenAll(taskList);

    // Looks like CombineDataParts was expecting a list, so convert and we're done!
    return CombineDataParts(dataParts.ToList());


private async Task<Data> GetDataPart(string key)
   
    using (var response = await client.GetData(key))
    
        return ProcessData(response.Stream);
       

这里有一些额外的说明:

SortedDictionary 确保对键进行排序,而此代码没有。此实现将按照名称在partNames 中的顺序返回值(引用:Tasks.WhenAll&lt;TResult&gt;(Task&lt;TResult&gt;[]) documentation 上的注释)。如果您想在此方法中强制执行特定排序(正如 SortedDictionary 在原始代码中所做的那样),您应该考虑使用类似 List.Sort 的方式对 partNames 进行排序 根据您的偏好,您可以在顶部使用另一个 LINQ 语句而不是 foreach 循环来节省几行:IEnumerable&lt;Task&lt;Data&gt;&gt; taskList = partNames.Select(name =&gt; GetDataPart(name));

【讨论】:

等等。所以保存在dataParts列表中的数据不会是有序的吗?我以为是因为任务以正确的顺序添加到列表中 @an007 啊,我明白了。正确的是,这些值以相同的任务顺序返回(来源:关于 [Tasks.WhenAll(Task[]) 文档](docs.microsoft.com/en-us/dotnet/api/…) 的备注)。我的意思是,排序取决于在调用此代码之前排序的列表。之前使用 SortedDictionary 的实现将强制执行排序,无论传入什么排序。我将更新我的注释以更清楚。

以上是关于Task.WhenAll 不等待任务完成[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

忽略在 Task.WhenAll 抛出异常的任务,只获取完成的结果

异步/等待死锁 Task.WaitAll 与 Task.WhenAll [重复]

为啥不等待 Task.WhenAll 抛出 AggregateException?

c# Task.WhenAll(tasks) 和 SemaphoreSlim - 如何知道所有任务何时已完全完成

合并 Observable 列表并等待所有完成

Task CancellationTokenSource和Task.WhenAll的应用