Muszę zmodyfikować istniejący program i zawiera następujący kod:
var inputs = events.Select(async ev => await ProcessEventAsync(ev))
.Select(t => t.Result)
.Where(i => i != null)
.ToList();
Ale wydaje mi się to bardzo dziwne, przede wszystkim użycie async
i await
w selekcji. Zgodnie z tą odpowiedzią Stephena Cleary'ego powinienem móc je odrzucić.
Następnie drugi, Select
który wybiera wynik. Czy nie oznacza to, że zadanie nie jest w ogóle asynchroniczne i jest wykonywane synchronicznie (tyle wysiłku na nic), czy też będzie wykonywane asynchronicznie, a po zakończeniu reszta zapytania zostanie wykonana?
Czy powinienem napisać powyższy kod jak poniżej według innej odpowiedzi Stephena Cleary :
var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)));
var inputs = tasks.Where(result => result != null).ToList();
i czy jest zupełnie tak samo?
var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))))
.Where(result => result != null).ToList();
Kiedy pracuję nad tym projektem, chciałbym zmienić pierwszy przykład kodu, ale nie jestem zbyt chętny do zmiany (najwyraźniej działającego) kodu asynchronicznego. Może po prostu martwię się o nic, a wszystkie 3 próbki kodu robią dokładnie to samo?
ProcessEventsAsync wygląda następująco:
async Task<InputResult> ProcessEventAsync(InputEvent ev) {...}
źródło
Task<InputResult>
o to,InputResult
że jest to klasa niestandardowa.Select
o wynikach zadań wykonanych wcześniejWhere
.Result
właściwości zadaniaOdpowiedzi:
Wezwanie do
Select
jest ważne. Te dwie linie są zasadniczo identyczne:(Istnieje niewielka różnica w sposobie wyrzucenia wyjątku synchronicznego
ProcessEventAsync
, ale w kontekście tego kodu nie ma to żadnego znaczenia).Oznacza to, że zapytanie jest blokowane. Więc to nie jest tak naprawdę asynchroniczne.
Rozbijając to:
najpierw rozpocznie operację asynchroniczną dla każdego zdarzenia. Następnie ta linia:
będzie czekać, aż te operacje zakończą się pojedynczo (najpierw czeka na operację pierwszego zdarzenia, potem następne, potem następne itd.).
To jest część, na której mi nie zależy, ponieważ blokuje, a także zawija wszelkie wyjątki
AggregateException
.Tak, te dwa przykłady są równoważne. Obaj rozpoczynają wszystkie operacje asynchroniczne (
events.Select(...)
), następnie asynchronicznie czekają na zakończenie wszystkich operacji w dowolnej kolejności (await Task.WhenAll(...)
), a następnie wykonują resztę pracy (Where...
).Oba te przykłady różnią się od oryginalnego kodu. Oryginalny kod jest blokujący i zawija wyjątki
AggregateException
.źródło
AggregateException
I otrzymam wiele oddzielnych wyjątków w drugim kodzie?Result
tym byłby zapakowanyAggregateException
.stuff.Select(x => x.Result);
zawait Task.WhenAll(stuff)
Istniejący kod działa, ale blokuje wątek.
tworzy nowe zadanie dla każdego wydarzenia, ale
blokuje wątek czekając na zakończenie każdego nowego zadania.
Z drugiej strony twój kod daje ten sam wynik, ale zachowuje asynchroniczność.
Tylko jeden komentarz do Twojego pierwszego kodu. Ta linia
utworzy jedno zadanie, więc zmienna powinna być nazwana w liczbie pojedynczej.
Wreszcie twój ostatni kod robi to samo, ale jest bardziej zwięzły
Dla odniesienia: Task.Wait / Task.WhenAll
źródło
tasks
zmiennej, masz całkowitą rację. Okropny wybór, nie są to nawet zadania, ponieważ są od razu oczekiwane. Po prostu zostawię pytanie tak, jak jestPrzy obecnych metodach dostępnych w Linq wygląda to dość brzydko:
Miejmy nadzieję, że kolejne wersje .NET dostarczą bardziej eleganckiego narzędzia do obsługi zbiorów zadań i zadań związanych z kolekcjami.
źródło
Użyłem tego kodu:
lubię to:
źródło
Func<TSource, Task<TResult>> method
zawieraćother params
wspomnianego w drugim bicie kodu?Select()
, więc jest to eleganckie drop-in.Wolę to jako metodę rozszerzenia:
Aby można go było używać z łańcuchem metod:
źródło
Wait
gdy w rzeczywistości nie czeka. Tworzy zadanie, które jest zakończone, gdy wszystkie zadania są ukończone. Nazwij toWhenAll
tak, jakTask
metoda, którą naśladuje. Nie ma też sensu, aby ta metoda byłaasync
. Po prostu zadzwońWhenAll
i skończ z tym.WhenAll
zwraca listę ocenioną (nie jest oceniana leniwie), można ustawić argument, aby używałTask<T[]>
zwracanego typu, aby to oznaczyć. Oczekiwany, nadal będzie mógł korzystać z Linq, ale również informuje, że nie jest leniwy.