Mam listę zadań, które utworzyłem w ten sposób:
public async Task<IList<Foo>> GetFoosAndDoSomethingAsync()
{
var foos = await GetFoosAsync();
var tasks = foos.Select(async foo => await DoSomethingAsync(foo)).ToList();
...
}
Po użyciu .ToList()
wszystkie zadania powinny się rozpocząć. Teraz chcę poczekać na ich zakończenie i zwrócić wyniki.
Działa to w powyższym ...
bloku:
var list = new List<Foo>();
foreach (var task in tasks)
list.Add(await task);
return list;
Robi, co chcę, ale wydaje się to raczej niezdarne. Wolałbym napisać coś prostszego:
return tasks.Select(async task => await task).ToList();
... ale to się nie kompiluje. czego mi brakuje? A może po prostu nie można tego wyrazić w ten sposób?
c#
linq
async-await
Matt Johnson-Pint
źródło
źródło
DoSomethingAsync(foo)
kolejno dla każdego foo, czy jest to kandydat do Parallel.ForEach <Foo> ?Parallel.ForEach
blokuje. Wzorzec tutaj pochodzi z asynchronicznego wideo C # Jona Skeeta w witrynie Pluralsight . Wykonuje się równolegle bez blokowania..ToList()
jeśli mam zamiar użyćWhenAll
.)DoSomethingAsync
jest napisana, lista może być wykonywana równolegle lub nie. Udało mi się napisać metodę testową, która była i wersję, której nie było, ale w każdym przypadku zachowanie jest podyktowane samą metodą, a nie delegatem tworzącym zadanie. Przepraszam za zamieszanie. Jeśli jednakDoSomethingAsyc
wróciTask<Foo>
, toawait
obecność delegata nie jest absolutnie konieczna ... Myślę, że to był główny punkt, który zamierzałem zrobić.Odpowiedzi:
LINQ nie działa idealnie z
async
kodem, ale możesz to zrobić:var tasks = foos.Select(DoSomethingAsync).ToList(); await Task.WhenAll(tasks);
Jeśli wszystkie zadania zwracają ten sam typ wartości, możesz nawet zrobić to:
var results = await Task.WhenAll(tasks);
co jest całkiem miłe.
WhenAll
zwraca tablicę, więc uważam, że twoja metoda może zwrócić wyniki bezpośrednio:return await Task.WhenAll(tasks);
źródło
var tasks = foos.Select(foo => DoSomethingAsync(foo)).ToList();
var tasks = foos.Select(DoSomethingAsync).ToList();
Select
. Ale większość nie lubiWhere
.async
do zredukowania wątków; jeśli jest związany z procesorem i znajduje się już w wątku w tle,async
nie przyniesie żadnych korzyści.Aby rozwinąć odpowiedź Stephena, stworzyłem następującą metodę rozszerzenia, aby zachować płynny styl LINQ. Możesz to zrobić
await someTasks.WhenAll() namespace System.Linq { public static class IEnumerableExtensions { public static Task<T[]> WhenAll<T>(this IEnumerable<Task<T>> source) { return Task.WhenAll(source); } } }
źródło
ToArrayAsync
Jednym z problemów z Task.WhenAll jest to, że stworzyłoby to równoległość. W większości przypadków może być nawet lepiej, ale czasami chcesz tego uniknąć. Na przykład odczytywanie danych partiami z bazy danych i wysyłanie danych do niektórych zdalnych usług internetowych. Nie chcesz ładować wszystkich partii do pamięci, ale trafisz do bazy danych po przetworzeniu poprzedniej partii. Więc musisz przełamać asynchroniczność. Oto przykład:
var events = Enumerable.Range(0, totalCount/ batchSize) .Select(x => x*batchSize) .Select(x => dbRepository.GetEventsBatch(x, batchSize).GetAwaiter().GetResult()) .SelectMany(x => x); foreach (var carEvent in events) { }
Uwaga .GetAwaiter (). GetResult () konwertuje go na synchronizację. DB zostałby uderzony leniwie tylko po przetworzeniu zdarzeń batchSize.
źródło
Użyj
Task.WaitAll
lubTask.WhenAll
cokolwiek jest odpowiednie.źródło
Task.WaitAll
blokuje, nie jest oczekiwany i nie będzie działać z plikiemTask<T>
.WhenAll
?Zadanie Kiedy wszyscy powinni tutaj załatwić sprawę.
źródło