【问题标题】:Task.WhenAll creates duplicates for Task<ConcurrentDictionary>Task.WhenAll 为 Task<ConcurrentDictionary> 创建重复项
【发布时间】:2018-07-01 04:40:40
【问题描述】:

创建任务列表的类,每个任务返回 ConcurrentDictionary

public List<Task<ConcurrentDictionary<int, object>>> GetDictionaries()
{
  var results = new ConcurrentDictionary<int, object>();
  var tasks = new List<Task<ConcurrentDictionary<int, object>>>();

  for (var k = 0; k < 10; k++)
  {
    tasks.Add(Task.Run(() =>
    {
      var done = false;
      var data = new Object();
      var eventCallback = (int id) => { data.id = id; done = true; };

      Client.asyncEvent += eventCallback;
      Client.initiateAsyncEvent(k);
      while (done == false);
      Client.asyncEvent -= eventCallback;

      results[k] = data; 
      return results;
    }));
  }

  return tasks;
}

调用事件(任务)10次,等待该事件的回调,将结果添加到字典“results”中。

我们执行 10 个事件(任务),因此应该在字典中获取 10 个项目,但是当我将所有任务的字典与 When.All 合并时,列表包含 100 个项目而不是 10 个。

var tasks = GetDictionaries();

var plainListOfResults = Task
  .WhenAll(tasks)
  .Result
  .SelectMany(o => o.Keys)
  .ToList();

// Expected: [0,1,2,3,4,5,6,7,8,9]
// Actual: [0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9 ... 0,1,2,3,4,5,6,7,8,9]

问题

  1. 为什么 10 个任务创造的结果是应有的 10 倍?
  2. 为什么,当我将 ConcurrentDictionary 替换为 Dictionary 时,此代码会按预期工作?

【问题讨论】:

  • 这是一大堆代码,其中大部分与问题无关,您能否创建一个最小的示例来解释您要解释的内容?因为它不明显
  • @TheGeneral 试图简化代码并重新表述问题。
  • 直觉上,我认为 Dictionary 和 ConcurrentDictionary 在如何合并具有相同键的项目方面存在差异。看起来 ConcurrentDictionary 为每个线程创建了同一个字典的实例,当我合并线程时会导致 When.All 它认为所有对象都是唯一的,即使使用相同的键也是如此。

标签: c# multithreading concurrency interactive-brokers tws


【解决方案1】:

每个Task 都返回整个ConcurrentDictionary,因此当您从Task.WhenAll 获取结果集时,它包含相同的字典10 次。

一些补充说明:

while (done == false); 太糟糕了。它可能在等待时将您的 CPU 固定在 100%。如果您要将基于事件的异步转换为基于任务的异步,请将您的事件转换为任务,或使用TaskCompletionSource

https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/interop-with-other-asynchronous-patterns-and-types

如果您可以重构使异步方法只返回值,例如ValueTuples、Tuples、KeyValuePairs、匿名类型或您自己的类型,并且在它们运行时不要修改字典,你也可以放弃ConcurrentDictionary,只在Task.WhenAll之后使用ToDictionary从结果集中创建字典。

【讨论】:

  • 其实我用的是TaskCompletionSource,但是TheGeneral要求我简化代码,所以我用最简单的伪代码替换了一些C#和IB的东西,只是为了说明一个想法。您可以点击下方的“X 小时前编辑”来查看我的帖子的第一个版本 :)
  • 恐怕,我不能从这个方法返回单个值,它依赖于执行大约一分钟并将值返回给 eventCallback 的 API,所以该方法应该从 API 收集所有值并以 Enumerable 的形式返回它们。它适用于 List 和 Dictionary,但使用 ConcurrentDictonary 会创建重复项
  • 我将尝试检查我可以在这里使用哪些异步模式,如果我找不到任何合适的,我将对在没有任务的循环中创建的多个事件使用单个回调,所以所有值将在单个线程中累积在 ConcurrentDictionary 中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-04
  • 1970-01-01
  • 2021-03-08
  • 2014-12-04
  • 2019-08-25
  • 1970-01-01
  • 2014-09-08
相关资源
最近更新 更多