Task.WhenAll 为任务创建副本<ConcurrentDictionary>

Task.WhenAll creates duplicates for Task<ConcurrentDictionary>

Class 创建任务列表,每个任务 returns 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 时,此代码按预期工作?

每个 Task 都是 return 整个 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

如果您可以重构,以便异步方法只是 return 值,例如 ValueTuples、Tuples、KeyValuePairs、匿名类型或您自己的类型,并且在 运行 时不要修改字典,您也可以放弃 ConcurrentDictionary 并仅根据 Task.WhenAll 之后的 ToDictionary 结果集创建字典。