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<K,V>
类不是线程安全的。您能否尝试在SaveDatainDict
方法的开头添加lock (dataList)
,看看是否有什么不同?
@TheodorZoulias:这是个糟糕的主意。使用SortedDictionary
需要锁定dataList[part] = (something);
操作,但您建议在await GetDataPart(part)
期间也持有锁定
老实说,我认为这不是字典线程问题。我猜所有的key
s 都是独一无二的?我很好奇您是否在return CombineDataParts
行上设置了断点,如果您在TaskList
中看到正确数量的任务并且所有IsCompletedSuccessfully
的值为true。他们中的任何一个都有例外吗?
如果它是线程问题,您能否通过保留GetDataPart
中的List<Task<Data>>
来消除中间数据结构和方法?然后Task.WhenAll
will return an array of Data
可以在其上运行Select
语句。
@BenVoigt 是的,你是对的。只是按照我建议的方式添加 lock
可能甚至无法编译。
【参考方案1】:
为了让未来的读者更清楚,将我的评论分解为完整的答案...
正如@TheodorZoulais 所暗示的,这可能是SortedDictionary
不是线程安全的问题。这意味着当任务本身完成时,SortedDictionary
不能保证立即在线程间一致地反映状态,这在这里引起了头疼。这可能可以通过锁定正确的位置来解决……但有一种更简单的方法。
由于您只是使用SortedDictionary
来组合Task
s 的结果,我们可以通过稍微不同的使用Task.WhenAll
来完全避开问题。 Task.WhenAll
做了两件非常方便的事情:
-
为我们返回一个任务到
await
如果我们传递给Task.WhenAll
的Task
s 有结果,它会将这些结果合并到一个可枚举的结果中(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<TResult>(Task<TResult>[])
documentation 上的注释)。如果您想在此方法中强制执行特定排序(正如 SortedDictionary
在原始代码中所做的那样),您应该考虑使用类似 List.Sort
的方式对 partNames
进行排序
根据您的偏好,您可以在顶部使用另一个 LINQ 语句而不是 foreach 循环来节省几行:IEnumerable<Task<Data>> taskList = partNames.Select(name => GetDataPart(name));
【讨论】:
等等。所以保存在dataParts列表中的数据不会是有序的吗?我以为是因为任务以正确的顺序添加到列表中 @an007 啊,我明白了。正确的是,这些值以相同的任务顺序返回(来源:关于 [Tasks.WhenAllSortedDictionary
的实现将强制执行排序,无论传入什么排序。我将更新我的注释以更清楚。以上是关于Task.WhenAll 不等待任务完成[关闭]的主要内容,如果未能解决你的问题,请参考以下文章
忽略在 Task.WhenAll 抛出异常的任务,只获取完成的结果
异步/等待死锁 Task.WaitAll 与 Task.WhenAll [重复]
为啥不等待 Task.WhenAll 抛出 AggregateException?