2016-01-26 18 views
48

muszę zmodyfikować istniejący program i zawiera następujący kod:asynchroniczny czekają w LINQ wybierz

var inputs = events.Select(async ev => await ProcessEventAsync(ev)) 
        .Select(t => t.Result) 
        .Where(i => i != null) 
        .ToList(); 

Ale to wydaje się bardzo dziwne dla mnie, przede wszystkim wykorzystania async i await w select. Według this answer autorstwa Stephena Cleary powinienem móc je upuścić.

Następnie drugi Select, który wybiera wynik. Czy to nie oznacza, że ​​zadanie nie jest w ogóle asynchroniczne i jest wykonywane synchronicznie (tak wiele wysiłku za nic), czy zadanie będzie wykonywane asynchronicznie, a kiedy zostanie wykonane, wykonywane jest pozostałe zapytanie?

powinienem napisać powyższy kod jak następujących według another answer by Stephen Cleary:

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))); 
var inputs = tasks.Where(result => result != null).ToList(); 

i jest to zupełnie tak samo jak ten?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)))) 
             .Where(result => result != null).ToList(); 

a ja pracuję nad tym projektem chciałbym zmienić pierwszą próbkę kodu, ale nie jestem zbyt chętni na zmianę roboczą (podobno) 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) {...} 
+0

Jaki jest typ zwrotu ProceesEventAsync? – tede24

+0

@ tede24 To 'Zadanie ' z 'InputResult' jest niestandardową klasą. –

+0

Twoje wersje są znacznie łatwiejsze do odczytania w mojej opinii. Jednak zapomniałeś "wybrać" wyniki z zadań przed swoim "Gdzie". – Max

Odpowiedz

48
var inputs = events.Select(async ev => await ProcessEventAsync(ev)) 
        .Select(t => t.Result) 
        .Where(i => i != null) 
        .ToList(); 

Ale to wydaje się bardzo dziwne dla mnie, przede wszystkim wykorzystania asynchroniczny i czekać w selekcji. Zgodnie z tą odpowiedzią Stephena Cleary'ego powinienem móc je upuścić.

Połączenie z Select jest ważne. Te dwie linie są w zasadzie identyczne:

events.Select(async ev => await ProcessEventAsync(ev)) 
events.Select(ev => ProcessEventAsync(ev)) 

(Istnieje niewielka różnica w zakresie sposobu synchroniczny wyjątek byłby wyrzucony z ProcessEventAsync, ale w kontekście tego kodu nie ma znaczenia w ogóle.)

Następnie drugi Wybierz, który wybiera wynik. Czy to nie oznacza, że ​​zadanie nie jest w ogóle asynchroniczne i jest wykonywane synchronicznie (tak wiele wysiłku za nic), czy zadanie będzie wykonywane asynchronicznie, a kiedy zostanie wykonane, wykonywane jest pozostałe zapytanie?

Oznacza to, że zapytanie jest blokowane. Więc nie jest tak naprawdę asynchroniczny.

złamanie go:

var inputs = events.Select(async ev => await ProcessEventAsync(ev)) 

najpierw rozpocząć operację asynchroniczną dla każdego zdarzenia. Potem ta linia:

    .Select(t => t.Result) 

będą czekać na tych operacji, aby zakończyć po jednym na raz (pierwszy czeka na działania pierwszego zdarzenia, potem następny, potem następny, etc).

Jest to część, której nie obchodzi, ponieważ blokuje i również zawijać wszelkie wyjątki w AggregateException.

i czy jest całkowicie taki sam?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))); 
var inputs = tasks.Where(result => result != null).ToList(); 

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)))) 
             .Where(result => result != null).ToList(); 

Tak, te dwa przykłady są równoważne. Obydwa rozpoczynają wszystkie operacje asynchroniczne (events.Select(...)), a następnie asynchronicznie czekają na zakończenie wszystkich operacji w dowolnej kolejności (await Task.WhenAll(...)), a następnie kontynuują dalszą część pracy (Where...).

Oba te przykłady różnią się od oryginalnego kodu. Oryginalny kod jest blokowany i będzie pakował wyjątki w AggregateException.

+0

Pozdrawiam, że to wyczyściłem! Tak więc zamiast wyjątków zapakowanych w 'wyjątek AggregateException 'uzyskałbym kilka oddzielnych wyjątków w drugim kodzie? –

+1

@AlexanderDerck: Nie, zarówno w starym, jak i nowym kodzie, podniesiony zostałby tylko pierwszy wyjątek. Ale z 'Result' byłby zawarty w' AggregateException'. –

12

Istniejący kod działa, ale blokuje nić.

.Select(async ev => await ProcessEventAsync(ev)) 

tworzy nowe zadanie dla każdego zdarzenia, ale

.Select(t => t.Result) 

blokuje wątek czeka na każdego nowego zadania do końca.

Z drugiej strony kod generuje taki sam wynik, ale zachowuje asynchronię.

Tylko jeden komentarz na temat pierwszego kodu. Ta linia tworzy jedno zadanie, aby zmienna miała nazwę w liczbie pojedynczej.

Wreszcie ostatnia kod uczynić to samo, ale jest bardziej zwięzły

Dla porównania: Task.Wait/Task.WhenAll

+0

Tak więc pierwszy blok kodu jest faktycznie wykonywany synchronicznie? –

+1

Tak, ponieważ uzyskanie dostępu do wyniku tworzy czekanie, które blokuje wątek. Z drugiej strony Kiedy tworzy nowe zadanie, możesz na to czekać. – tede24

+1

Wracając do tego pytania i patrząc na twoją uwagę na temat nazwy zmiennej "zadania", masz całkowitą rację. Okropny wybór, nie są nawet zadaniami, ponieważ oczekują od razu. Po prostu zostawię to tak, jakby to było –

2

z aktualnymi metodami dostępnymi w LINQ to wygląda dość brzydki:

 var tasks = items.Select(
      async item => new 
      { 
       Item = item, 
       IsValid = await IsValid(item) 
      }); 
     var tuples = await Task.WhenAll(tasks); 
     var validItems = tuples 
      .Where(p => p.IsValid) 
      .Select(p => p.Item) 
      .ToList(); 

Mam nadzieję, że następujące wersje .NET będzie wymyślić bardziej elegancki oprzyrządowania do obsługi zbiory zadań i zadań kolekcjach.