Task.WhenAll为Task <concurrentdictionary>创建重复项

时间:2018-07-01 04:40:40

标签: c# multithreading concurrency interactive-brokers tws

创建任务列表的类,每个任务返回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次,等待该事件的回调,然后将结果添加到字典“结果”中。

我们执行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. 为什么,当我用Dictionary替换ConcurrentDictionary时,此代码可以按预期工作?

1 个答案:

答案 0 :(得分:3)

每个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

如果您可以重构,以便异步方法仅返回值,例如ValueTupleTupleKeyValuePair,匿名类型或您自己的类型,则不要修改字典运行时,您也可以抛弃ConcurrentDictionary并仅在ToDictionary之后使用Task.WhenAll从结果集中创建字典。