Mam 3 zadania:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
Wszystkie muszą uruchomić się, zanim mój kod będzie mógł kontynuować, a ja też potrzebuję wyników od każdego z nich. Żaden z wyników nie ma ze sobą nic wspólnego
Jak zadzwonić i poczekać na wykonanie 3 zadań, a następnie na uzyskanie wyników?
c#
.net
async-await
task-parallel-library
.net-4.5
Ian Vink
źródło
źródło
Odpowiedzi:
Po użyciu
WhenAll
możesz wyciągnąć wyniki indywidualnie za pomocąawait
:Możesz także użyć
Task.Result
(ponieważ wiesz, że do tego momentu wszystkie zostały pomyślnie ukończone). Jednak zalecam używanie,await
ponieważ jest wyraźnie poprawne, aResult
może powodować problemy w innych scenariuszach.źródło
WhenAll
całkowicie usunąć z tego; Oczekuje, że nie przejdziesz 3 późniejszych zadań, dopóki zadania nie zostaną zakończone.Task.WhenAll()
pozwala uruchomić zadanie w trybie równoległym . Nie rozumiem, dlaczego @Servy zaproponował, aby go usunąć. BezWhenAll
nich będą biegać jeden po drugimcatTask
Już działa, zanim zostanie zwróconyFeedCat
. Każde z tych podejść będzie działać - jedynym pytaniem jest, czy chceszawait
je pojedynczo, czy wszystkie razem. Obsługa błędów jest nieco inna - jeśli go użyjeszTask.WhenAll
, zrobiawait
to wszystko, nawet jeśli jeden z nich zawiedzie wcześnie.WhenAll
nie ma wpływu na czas wykonywania operacji ani sposób ich wykonywania. To tylko ma żadnej możliwości dokonania w jaki sposób wyniki są przestrzegane. W tym konkretnym przypadku jedyną różnicą jest to, że błąd w jednej z dwóch pierwszych metod spowodowałby zgłoszenie wyjątku w tym stosie wywołań wcześniej w mojej metodzie niż w przypadku Stephena (chociaż ten sam błąd byłby zawsze zgłaszany, jeśli byłyby jakieś ).Tylko
await
trzy zadania osobno, po uruchomieniu ich wszystkich.źródło
Task.WhenAll
zmian dosłownie nic na temat zachowania programu, w jakikolwiek możliwy do zaobserwowania sposób. Jest to czysto zbędne wywołanie metody. Możesz go dodać, jeśli chcesz, jako wybór estetyczny, ale nie zmienia to, co robi kod. Czas wykonania kodu będzie identyczny z tym wywołaniem metody lub bez niego (cóż, technicznie będzie to naprawdę niewielki narzut związany z wywoływaniemWhenAll
, ale powinno to być nieistotne), co spowoduje , że ta wersja będzie działać nieco dłużej niż ta wersja.WhenAll
jest zmianą czysto estetyczną. Jedyną zauważalną różnicą w zachowaniu jest to, czy czekasz na zakończenie późniejszych zadań, jeśli wcześniejsze zadanie zawiedzie, co zwykle nie jest konieczne. Jeśli nie wierzysz w liczne wyjaśnienia, dlaczego twoje stwierdzenie nie jest prawdziwe, możesz po prostu uruchomić kod dla siebie i przekonać się, że to nieprawda.Jeśli używasz C # 7, możesz użyć przydatnej metody otoki takiej jak ta ...
... aby włączyć taką wygodną składnię, gdy chcesz czekać na wiele zadań z różnymi typami zwrotów. Oczywiście musiałbyś wykonać wiele przeciążeń, aby czekała na nich różna liczba zadań.
Jednak zapoznaj się z odpowiedzią Marca Gravella na kilka optymalizacji dotyczących ValueTask i już wykonanych zadań, jeśli zamierzasz zamienić ten przykład w coś prawdziwego.
źródło
Task.WhenAll()
nie zwraca krotki. Jeden jest konstruowany zResult
właściwości dostarczonych zadań po zakończeniu zadania zwróconego przezTask.WhenAll()
..Result
połączeń zgodnie z rozumowaniem Stephena, aby uniknąć kopiowania przykładu przez innych ludzi, którzy utrwalają złą praktykę.Biorąc pod uwagę trzy zadania -
FeedCat()
,SellHouse()
iBuyCar()
istnieją dwa ciekawe przypadki: albo wszystkie one kompletne synchronicznie (z jakiegoś powodu, może buforowania lub błędu), albo ich nie ma.Powiedzmy, że mamy, z pytania:
Teraz prostym podejściem byłoby:
ale ... to nie jest wygodne do przetwarzania wyników; zazwyczaj chcielibyśmy
await
tego:ale powoduje to duże obciążenie i przydziela różne tablice (w tym
params Task[]
tablicę) i listy (wewnętrznie). Działa, ale to nie jest świetne IMO. Na wiele sposobów łatwiej jest korzystać zasync
operacji i tylkoawait
po kolei:W przeciwieństwie do niektórych z powyższych komentarzy, użycie
await
zamiast nieTask.WhenAll
ma żadnego znaczenia w sposobie działania zadań (jednocześnie, sekwencyjnie itp.). Na najwyższym poziomieTask.WhenAll
poprzedza dobrą obsługę kompilatora dlaasync
/await
i był przydatny, gdy te rzeczy nie istniały . Jest to również przydatne, gdy masz dowolny zestaw zadań, a nie 3 zadania dyskretne.Ale: nadal mamy problem, który
async
/await
generuje dużo hałasu kompilatora dla kontynuacji. Jeśli jest prawdopodobne, że zadania mogą faktycznie zostać wykonane synchronicznie, możemy to zoptymalizować, budując ścieżkę synchroniczną z asynchroniczną rezerwą:Takie podejście do „ścieżki synchronizacji z zastępowaniem asynchronicznym” jest coraz bardziej powszechne, szczególnie w kodach o wysokiej wydajności, w których synchroniczne uzupełnianie jest stosunkowo częste. Zauważ, że to wcale nie pomoże, jeśli zakończenie jest zawsze rzeczywiście asynchroniczne.
Obowiązują tutaj dodatkowe rzeczy:
w ostatnim C # wspólny wzorzec dla
async
metody rezerwowej jest zwykle implementowany jako funkcja lokalna:preferuje
ValueTask<T>
sięTask<T>
, jeśli istnieje duża szansa rzeczy nigdy całkowicie synchronicznie z wieloma różnymi wartościami powrotów:jeśli to możliwe, wolą
IsCompletedSuccessfully
sięStatus == TaskStatus.RanToCompletion
; teraz istnieje w .NET Core dlaTask
i wszędzie dlaValueTask<T>
źródło
Task
gdy wszystko jest zrobione bez użycia wyników.await
uzyskać „lepszą” semantykę wyjątków, przy założeniu, że wyjątki są rzadkie, ale znacząceMożesz przechowywać je w zadaniach, a następnie czekać na nich wszystkich:
źródło
var catTask = FeedCat()
wykonuje funkcjiFeedCat()
i nie przechowuje wyniku,catTask
abyawait Task.WhenAll()
część stała się bezużyteczna, ponieważ metoda już wykonała?W przypadku próby zarejestrowania wszystkich błędów upewnij się, że zachowałeś wiersz Task.WhenAll w kodzie, wiele komentarzy sugeruje, że możesz go usunąć i poczekać na poszczególne zadania. Task.WhenAll jest naprawdę ważny dla obsługi błędów. Bez tego wiersza potencjalnie pozostawiasz swój kod otwarty dla nieobserwowanych wyjątków.
Wyobraź sobie FeedCat zgłasza wyjątek w następującym kodzie:
W takim przypadku nigdy nie będziesz czekać na houseTask ani carTask. Istnieją tutaj 3 możliwe scenariusze:
SellHouse jest już pomyślnie zakończony, gdy FeedCat nie powiódł się. W takim przypadku wszystko w porządku.
SellHouse nie jest kompletny i w pewnym momencie kończy się niepowodzeniem. Wyjątek nie jest przestrzegany i zostanie ponownie wprowadzony w wątku finalizatora.
SellHouse nie jest kompletny i zawiera w sobie oczekiwania. W przypadku, gdy Twój kod działa w ASP.NET SellHouse zawiedzie, gdy tylko niektóre z oczekiwań zostaną w nim ukończone. Dzieje się tak, ponieważ po prostu wywołałeś fire & zapomnij, że kontekst połączenia i synchronizacji został utracony, gdy tylko awaria FeedCat się nie powiodła.
Oto błąd, który otrzymasz w przypadku (3):
W przypadku (2) pojawi się podobny błąd, ale z oryginalnym śledzeniem stosu wyjątku.
W przypadku platformy .NET 4.0 i nowszych można przechwytywać nieobserwowane wyjątki za pomocą TaskScheduler.UnobservedTaskException. W przypadku platformy .NET 4.5 i nowszych nieobserwowane wyjątki są domyślnie połykane, aw przypadku .NET 4.0 nieobserwowany wyjątek spowoduje awarię procesu.
Więcej informacji tutaj: Obsługa wyjątków zadań w .NET 4.5
źródło
Możesz użyć,
Task.WhenAll
jak wspomniano, lubTask.WaitAll
, w zależności od tego, czy wątek ma czekać. Spójrz na link, aby uzyskać wyjaśnienie obu.WaitAll vs WhenAll
źródło
Użyj,
Task.WhenAll
a następnie poczekaj na wyniki:źródło
Przekaż ostrzeżenie
Wystarczy szybki przegląd tych, którzy odwiedzają ten i inne podobne wątki, szukając sposobu na zrównoleglenie EntityFramework za pomocą async + czekaj + zestaw narzędzi do zadań : pokazany tutaj wzór jest solidny, jednak jeśli chodzi o specjalny płatek śniegu EF, nie będziesz wykonać równoległe wykonywanie, chyba że użyjesz osobnej (nowej) instancji kontekstu db w każdym zaangażowanym wywołaniu * Async ().
Jest to konieczne ze względu na ograniczenia projektowe kontekstów ef-db, które zabraniają jednoczesnego uruchamiania wielu zapytań w tej samej instancji kontekstu ef-db.
Wykorzystując już udzielone odpowiedzi, jest to sposób na upewnienie się, że zbierasz wszystkie wartości, nawet w przypadku, gdy jedno lub więcej zadań powoduje wyjątek:
Alternatywną implementacją, która ma mniej więcej taką samą charakterystykę wydajności, może być:
źródło
jeśli chcesz uzyskać dostęp do Cat, wykonaj następujące czynności:
Jest to bardzo proste do zrobienia i bardzo przydatne w użyciu, nie ma potrzeby szukania złożonego rozwiązania.
źródło
dynamic
diabeł. Jest przeznaczony do skomplikowanej interakcji COM i tym podobnych, i nie powinien być używany w żadnej sytuacji, w której nie jest to absolutnie potrzebne. Szczególnie jeśli zależy Ci na wydajności. Lub wpisz bezpieczeństwo. Lub refaktoryzacja. Lub debugowanie.