Страницы

Поиск по вопросам

среда, 19 декабря 2018 г.

Выполнение асинхронных методов параллельно

Коллеги, помогите с кодом.
Пишу метод получения кастомного набора словарей из БД, который должен вызвать ряд асинхронных методов, дождаться выполнения каждого из них, сформировать "словарь словарей" и вернуть результат. Сигнатура метода следующая:
public async Task>> GetLookups(IEnumerable requiredTypes)
Попытка 1. Запустить сразу кучу тасков, тянущих данные из БД, положить в IEnumerable, сделать await WhenAll() и сформировать словарь из результатов тасков провалилась. EF не дает запустить вторую асинхронную операцию, используя тот же контекст данных, в котором запущена первая.
Попытка 2
return requiredTypes .Select(async t => { IEnumerable lookup = await GetLookup(t); return new KeyValuePair>(t, lookup); }) .ToDictionary(item => item.Result.Key, item => item.Result.Value);
Не собирается, получаю ошибку "Contract extraction failed: async / iterator issue:". Почитал что есть такая трабла с контрактами, если cделать async метод который не использует await (хотя await вроде как используется внутри Select).
Попытка 3 Оставил почти тот же код. Только в конце костыль прилепил чтобы await "официально" присутствовал в методе
var dictionary = requiredTypes .Select(async t => { IEnumerable lookup = await GetLookup(t); return new KeyValuePair>(t, lookup); }) .ToDictionary(item => item.Result.Key, item => item.Result.Value);
return await Task.FromResult(dictionary);
Получаю дедлок в момент вызова Select(). Поток зависает навсегда.
Вариант 4 Убрал LINQ и сделал по простому. Все работает
var lookups = new Dictionary>(); foreach (var type in requiredTypes) { var lookup = await GetLookup(type); lookups.Add(type, lookup); }
return lookups;
Метод GetLookup() описан следующим образом
public async Task> GetLookup(LookupType type) { switch (type) { case LookupType.AgeCategory: return await _db.LookupAgeCategories.ToListAsync(); case LookupType.Distance: return await _db.LookupDistances.ToListAsync(); ... default: return await Task.FromResult(new List()); } }
Поясните плз, какие ошибки были допущены в вариантах? Какое решение наилучшее?


Ответ

Окей, смотрите. У нас есть ограничение, что методы GetLookup нужно вызывать параллельно. От него и будем плясать.
Попытка 1 — так делать оказалось нельзя, запускать все Task'и одновременно не катит. Окей.
Попытка 2. Здесь давайте посмотрим, что произошло после первого Select'а. Отвлечёмся на секундочку от того, что на вас ругаются Code Contracts. У вас по сути получился ленивый IEnumerable тасков. Затем вы пытаетесь применить к ним ToDictionary. Это синхронный метод, поэтому он пробежит ваш IEnumerable, создаст таски, и вызовет на них Result. Если создание тасков по идее может оказаться строго последовательным (хотя гарантии нет), то уж вызов Result — плохо, вы дожидаетесь результата синхронно, блокируя текущий поток.
Попытка 3. Та же проблема, await Task.FromResult(dictionary) ничего не меняет.
Вариант 4. Ну да, правильно.
Смотрите, если вы хотите использовать LINQ, вам нужно по сути асинхронный WhenAll, но который обязательно запускает пробегает свой список последовательно. Например, такое:
static class TaskEx { public async static Task WhenAllSequential(IEnumerable> tasks) { var results = new List(); foreach (var t in tasks) results.Add(await t); return results.ToArray(); } }

Комментариев нет:

Отправить комментарий