Task.WhenAll создает дубликаты для задачи<ConcurrentDictionary>

Класс, который создает список задач, каждая задача возвращает 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, этот код работает должным образом?

1 ответ

Решение

Каждый Task возвращается весь ConcurrentDictionary поэтому, когда вы получите набор результатов из Task.WhenAll, он содержит один и тот же словарь 10 раз.

Некоторые дополнительные заметки:

while (done == false); это ужасно Это, вероятно, привязывает ваш процессор на 100%, пока он ждет. Если вы конвертируете асинхронность на основе событий в асинхронность на основе задач, конвертируйте ваши события в задачи или используйте TaskCompletionSource

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

Если вы можете рефакторинг так, чтобы асинхронные методы просто возвращали значения, как ValueTuples, Tuples, KeyValuePairs, анонимные типы или ваши собственные типы, и не изменяйте словарь во время их работы, вы также можете отказаться от ConcurrentDictionary и просто создать словарь из набора результатов с ToDictionary после Task.WhenAll,

Другие вопросы по тегам