Raz po raz widzę, że mówienie, że używanie async
- await
nie tworzy żadnych dodatkowych wątków. To nie ma sensu, ponieważ jedynym sposobem, w jaki komputer wydaje się robić więcej niż jedną rzecz na raz, jest
- Właściwie robienie więcej niż 1 rzeczy na raz (wykonywanie równoległe, korzystanie z wielu procesorów)
- Symulowanie przez planowanie zadań i przełączanie się między nimi (zrób trochę A, trochę B, trochę A, itp.)
Jeśli więc async
- await
żaden z nich nie działa, to w jaki sposób może sprawić, że aplikacja będzie reagować? Jeśli jest tylko jeden wątek, wówczas wywołanie dowolnej metody oznacza oczekiwanie na zakończenie metody przed wykonaniem czegokolwiek innego, a metody wewnątrz tej metody muszą czekać na wynik przed kontynuowaniem itd.
c#
.net
multithreading
asynchronous
async-await
Pani Corlib
źródło
źródło
await
/async
działa bez tworzenia wątków.Odpowiedzi:
W rzeczywistości asynchronizacja / oczekiwanie nie jest aż tak magiczne. Cały temat jest dość obszerny, ale myślę, że poradzimy sobie z szybką, ale wystarczająco kompletną odpowiedzią na twoje pytanie.
Zajmijmy się prostym zdarzeniem kliknięcia przycisku w aplikacji Windows Forms:
Idę wyraźnie nie mówić o cokolwiek to jest
GetSomethingAsync
powraca do teraz. Powiedzmy, że jest to coś, co zakończy się, powiedzmy, po 2 sekundach.W tradycyjnym, niesynchronicznym świecie moduł obsługi zdarzeń kliknięcia przycisku wyglądałby mniej więcej tak:
Po kliknięciu przycisku w formularzu aplikacja będzie się zawieszać przez około 2 sekundy, podczas gdy my czekamy na zakończenie tej metody. Dzieje się tak, że „pompa komunikatów”, w zasadzie pętla, jest zablokowana.
Ta pętla nieustannie pyta okna: „Czy ktoś coś zrobił, na przykład przesunął mysz, kliknął coś? Czy muszę coś odmalować? Jeśli tak, powiedz mi!” a następnie przetwarza to „coś”. Ta pętla otrzymała komunikat, że użytkownik kliknął przycisk „button1” (lub równoważny typ wiadomości z systemu Windows) i ostatecznie wywołał naszą
button1_Click
metodę powyżej. Dopóki ta metoda nie powróci, ta pętla utknęła w oczekiwaniu. Zajmuje to 2 sekundy i podczas tego procesu nie są przetwarzane żadne wiadomości.Większość czynności związanych z oknami odbywa się za pomocą wiadomości, co oznacza, że jeśli pętla wiadomości przestanie pompować wiadomości, nawet na sekundę, użytkownik szybko to zauważy. Na przykład, jeśli umieścisz notatnik lub inny program na swoim własnym programie, a następnie ponownie, do twojego programu wysyłana jest seria komunikatów o farbie wskazujących, który region okna, który teraz nagle znów stał się widoczny. Jeśli pętla wiadomości, która przetwarza te wiadomości, czeka na coś zablokowana, wówczas nie jest wykonywane malowanie.
Więc jeśli w pierwszym przykładzie
async/await
nie tworzy nowych wątków, jak to robi?Cóż, dzieje się tak, że twoja metoda jest podzielona na dwie części. Jest to jeden z tych szerokich tematów, więc nie będę wchodził w zbyt szczegółowe szczegóły, ale wystarczy powiedzieć, że metoda jest podzielona na dwie rzeczy:
await
, w tym połączenie zGetSomethingAsync
await
Ilustracja:
Przeorganizowano:
Zasadniczo metoda działa w następujący sposób:
await
Wywołuje
GetSomethingAsync
metodę, która działa, i zwraca coś, co zakończy się w 2 sekundy w przyszłościDo tej pory nadal znajdujemy się w pierwotnym wywołaniu button1_Click, które dzieje się w głównym wątku wywoływanym z pętli wiadomości. Jeśli kod prowadzący do tego
await
zajmie dużo czasu, interfejs użytkownika nadal się zawiesza. W naszym przykładzie nie tak bardzoTo
await
słowo kluczowe, wraz z jakąś sprytną magią kompilatora, polega na tym, że w zasadzie jest to coś w stylu „Ok, wiesz co, po prostu wrócę tutaj z funkcji obsługi zdarzenia kliknięcia przycisku. czekam na) przejdź do zakończenia, daj mi znać, ponieważ wciąż mam trochę kodu do wykonania ".W rzeczywistości powiadomi klasę SynchronizationContext , że jest to zrobione, co w zależności od aktualnego kontekstu synchronizacji, który jest obecnie w grze, ustawi się w kolejce do wykonania. Klasa kontekstu używana w programie Windows Forms umieści ją w kolejce, używając kolejki, którą pompuje pętla komunikatów.
Powraca więc do pętli komunikatów, która może swobodnie pompować wiadomości, takie jak przesuwanie okna, zmiana jego rozmiaru lub klikanie innych przycisków.
Dla użytkownika interfejs użytkownika jest teraz ponownie responsywny, przetwarza inne kliknięcia przycisków, zmienia rozmiar i, co najważniejsze, przerysowuje , więc wydaje się, że nie zawiesza się.
await
i kontynuuje wykonywanie pozostałej części metody. Zauważ, że ten kod jest ponownie wywoływany z pętli komunikatów, więc jeśli ten kod zrobi coś długiego bezasync/await
prawidłowego użycia , ponownie zablokuje pętlę komunikatówPod maską znajduje się wiele ruchomych części, więc oto kilka linków do dodatkowych informacji, chciałem powiedzieć „jeśli będziesz tego potrzebować”, ale ten temat jest dość szeroki i dość ważne jest, aby znać niektóre z tych ruchomych części . Niezmiennie zrozumiesz, że asynchronizacja / oczekiwanie to wciąż nieszczelna koncepcja. Niektóre z podstawowych ograniczeń i problemów wciąż wyciekują do otaczającego kodu, a jeśli nie, zwykle trzeba debugować aplikację, która pęka losowo z pozornie nieuzasadnionego powodu.
OK, a co, jeśli
GetSomethingAsync
rozwinie wątek, który zakończy się za 2 sekundy? Tak, to oczywiście jest nowy wątek w grze. Ten wątek nie jest jednak spowodowany asynchronicznością tej metody, lecz dlatego, że programista tej metody wybrał wątek do implementacji kodu asynchronicznego. Prawie wszystkie asynchroniczne operacje we / wy nie używają wątku, używają różnych rzeczy.async/await
same w sobie nie rozwijają nowych wątków, ale oczywiście „rzeczy, na które czekamy” można zaimplementować za pomocą wątków.W .NET jest wiele rzeczy, które niekoniecznie same rozwijają wątek, ale nadal są asynchroniczne:
SomethingSomethingAsync
lubBeginSomething
aEndSomething
i nie jest toIAsyncResult
zaangażować.Zwykle te rzeczy nie używają nici pod maską.
OK, więc chcesz trochę „szerokich tematów”?
Cóż, zapytajmy Wypróbuj Roslyn o nasze kliknięcie przycisku:
Spróbuj Roslyn
Nie będę tutaj linkować w pełni wygenerowanej klasie, ale to dość krwawe rzeczy.
źródło
await
można go również używać w sposób, w jaki go opisujesz, ale ogólnie nie jest. Planowane są tylko połączenia zwrotne (w puli wątków) - między wywołaniem zwrotnym a żądaniem nie jest potrzebny żaden wątek.Wyjaśniam to w całości w moim poście na blogu There Is No Thread .
Podsumowując, nowoczesne systemy I / O intensywnie wykorzystują DMA (bezpośredni dostęp do pamięci). Istnieją specjalne, dedykowane procesory na kartach sieciowych, kartach graficznych, kontrolerach dysków twardych, portach szeregowych / równoległych itp. Procesory te mają bezpośredni dostęp do magistrali pamięci i obsługują odczyt / zapis całkowicie niezależnie od procesora. Procesor musi tylko powiadomić urządzenie o lokalizacji w pamięci zawierającej dane, a następnie może zrobić to samo, dopóki urządzenie nie zgłosi przerwania powiadamiającego procesor o zakończeniu odczytu / zapisu.
Gdy operacja jest w locie, procesor nie wykonuje żadnej pracy, a zatem nie ma wątku.
źródło
Task.Run
jest najbardziej odpowiedni dla akcji związanych z procesorem , ale ma także kilka innych zastosowań.To nie tak, że czekają, ani jedno z nich. Pamiętaj, że celem tego
await
nie jest uczynienie kodu synchronicznego magicznie asynchronicznym . Ma umożliwić korzystanie z tych samych technik, których używamy do pisania kodu synchronicznego podczas wywoływania kodu asynchronicznego . Oczekiwanie polega na tym, aby kod korzystający z operacji o wysokim opóźnieniu wyglądał jak kod korzystający z operacji o niskim opóźnieniu . Te operacje o dużym opóźnieniu mogą dotyczyć wątków, mogą być na sprzęcie specjalnego przeznaczenia, mogą rozrywać swoją pracę na małe kawałki i umieszczać ją w kolejce komunikatów do przetworzenia przez wątek interfejsu użytkownika później. Robią coś, aby osiągnąć asynchronię, ale onito oni to robią. Oczekiwanie pozwala tylko skorzystać z tej asynchronii.Myślę też, że brakuje ci trzeciej opcji. My, starzy ludzie - dziś dzieci z muzyką rapową powinniśmy zejść z trawnika itp. - Pamiętamy świat Windows na początku lat 90. Nie było maszyn wieloprocesorowych i harmonogramów wątków. Chciałeś uruchomić dwie aplikacje systemu Windows jednocześnie, musiałeś ustąpić . Wielozadaniowość była kooperatywna . System operacyjny informuje proces, który ma zostać uruchomiony, a jeśli jest źle wychowany, głosi wszystkie pozostałe procesy. Działa, dopóki się nie poddaje, i jakoś musi wiedzieć, jak podnieść to, co zostało przerwane, gdy następnym razem system operacyjny kontroluje go z powrotem. Jednowątkowy kod asynchroniczny jest podobny, z „oczekuj” zamiast „wydajnością”. Oczekiwanie oznacza „Będę pamiętać, gdzie tu skończyłem, i pozwolę komuś innemu uciec na chwilę; oddzwoń, gdy zadanie, na które czekam, jest zakończone, a ja wybiorę to, co przerwałem”. Myślę, że widać, jak to sprawia, że aplikacje są bardziej responsywne, tak jak w Windows 3 dni.
Istnieje klucz, którego brakuje. Metoda może powrócić przed zakończeniem pracy . To jest właśnie istota asynchronii. Metoda powraca, zwraca zadanie, które oznacza „ta praca jest w toku; powiedz mi, co mam zrobić po jej zakończeniu”. Praca metody nie jest wykonywana, nawet jeśli powróciła .
Przed operatorem oczekującym trzeba było napisać kod, który wyglądał jak spaghetti przepleciony szwajcarskim serem, aby poradzić sobie z faktem, że mamy pracę do wykonania po zakończeniu, ale zwrot i zakończenie są zsynchronizowane . Oczekiwanie pozwala napisać kod, który wygląda jak zwrot i zakończenie są zsynchronizowane, bez faktycznej synchronizacji.
źródło
yield
słowa kluczowego. Zarównoasync
metody, jak i iteratory w języku C # są formą coroutine , która jest ogólnym terminem na funkcję, która wie, jak zawiesić bieżącą operację w celu jej późniejszego wznowienia. Wiele języków ma dziś koruptyny lub korupcyjne przepływy kontrolne.Bardzo się cieszę, że ktoś zadał to pytanie, ponieważ przez najdłuższy czas wierzyłem również, że wątki są niezbędne do współbieżności. Kiedy po raz pierwszy zobaczyłem pętle zdarzeń , myślałem, że to kłamstwo. Pomyślałem sobie: „nie ma możliwości, aby ten kod był współbieżny, jeśli działa w jednym wątku”. Pamiętaj, że dzieje się to po tym, jak już przeszedłem walkę o zrozumienie różnicy między współbieżnością a równoległością.
Po badaniach własną rękę, w końcu znalazłem brakujący kawałek:
select()
. Konkretnie, IO multipleksowanie, realizowane przez różnych jąder pod różnymi nazwami:select()
,poll()
,epoll()
,kqueue()
. Są to wywołania systemowe, które mimo różnych szczegółów implementacji, umożliwiają przekazanie zestawu deskryptorów plików do obejrzenia. Następnie możesz wykonać kolejne wywołanie, które blokuje się, dopóki nie zmieni się jeden z obserwowanych deskryptorów plików.Zatem można poczekać na zestaw zdarzeń we / wy (główna pętla zdarzeń), obsłużyć pierwsze zdarzenie, które się zakończy, a następnie przywrócić kontrolę nad pętlą zdarzeń. Wypłukać i powtórzyć.
Jak to działa? Krótka odpowiedź brzmi: to magia jądra i sprzętu. Oprócz procesora w komputerze znajduje się wiele komponentów, które mogą działać równolegle. Jądro może kontrolować te urządzenia i komunikować się bezpośrednio z nimi, aby odbierać określone sygnały.
Te wywołania systemowe multipleksowania IO są podstawowym elementem budującym jednowątkowe pętle zdarzeń, takie jak node.js lub Tornado. Gdy jesteś
await
funkcją, obserwujesz określone zdarzenie (zakończenie tej funkcji), a następnie dajesz kontrolę z powrotem do głównej pętli zdarzeń. Po zakończeniu wydarzenia, które oglądasz, funkcja (ostatecznie) rozpoczyna się od miejsca, w którym została przerwana. Funkcje umożliwiające zawieszanie i wznawianie obliczeń w ten sposób nazywane są coroutines .źródło
await
iasync
używaj Zadań, a nie Wątków.Framework ma pulę wątków gotowych do wykonania niektórych prac w postaci obiektów Task ; przesłanie Zadania do puli oznacza wybranie wolnego, już istniejącego wątku 1 , aby wywołać metodę działania zadania.
Utworzenie zadania polega na utworzeniu nowego obiektu, znacznie szybciej niż utworzenie nowego wątku.
Biorąc pod uwagę zadania można dołączyć Kontynuacja do niego, że jest to nowy Task obiekt do być wykonywane raz końce nici.
Od czasu
async/await
użycia Zadań nie tworzą nowego wątku.Chociaż technika programowania przerwań jest szeroko stosowana w każdym nowoczesnym systemie operacyjnym, nie sądzę, aby były one tutaj istotne.
Możesz mieć dwa zadania związane z CPU wykonywane równolegle (właściwie przeplecione) w jednym procesorze
aysnc/await
.Nie można tego wyjaśnić po prostu faktem, że system operacyjny obsługuje kolejkowanie IORP .
Ostatnim razem, gdy sprawdziłem metody kompilatora przekształcone
async
w DFA , praca jest podzielona na etapy, z których każdy kończy sięawait
instrukcją. Rozpoczyna swoje zadanie i dołączyć go do kontynuacji, aby wykonać następny krok.await
Jako przykład koncepcji, oto przykład pseudokodu.
Upraszcza się rzeczy, ponieważ nie pamiętam dokładnie wszystkich szczegółów.
Przekształca się w coś takiego
1 W rzeczywistości pula może mieć zasady tworzenia zadań.
źródło
Nie zamierzam konkurować z Erikiem Lippertem ani Lasse V. Karlsenem i innymi, chciałbym tylko zwrócić uwagę na inny aspekt tego pytania, który moim zdaniem nie został wyraźnie wymieniony.
Korzystanie
await
z niej samodzielnie nie powoduje magicznej reakcji aplikacji. Jeśli cokolwiek zrobisz w metodzie, na którą czekasz z bloków wątków interfejsu użytkownika, nadal będzie blokować twój interfejs użytkownika w taki sam sposób, jak zrobiłaby to wersja nieoczekiwana .Musisz napisać swoją oczekiwaną metodę specjalnie, aby albo odrodził się nowy wątek, albo użył czegoś takiego jak port zakończenia (który zwróci wykonanie w bieżącym wątku i wezwie coś innego do kontynuacji za każdym razem, gdy port zakończenia zostanie zgłoszony). Ale tę część dobrze wyjaśniono w innych odpowiedziach.
źródło
Oto, jak to wszystko widzę, może nie być super technicznie dokładne, ale pomaga mi przynajmniej :).
Istnieją dwa typy przetwarzania (obliczenia), które mają miejsce na komputerze:
Kiedy więc piszemy fragment kodu źródłowego, po kompilacji, w zależności od używanego obiektu (i to jest bardzo ważne), przetwarzanie będzie powiązane z procesorem lub we / wy , a w rzeczywistości może być powiązane z kombinacją obie.
Kilka przykładów:
FileStream
obiektu (który jest Strumieniem), przetwarzanie będzie powiedziane, 1% związane z procesorem i 99% związane z IO.NetworkStream
obiektu (który jest Strumieniem), przetwarzanie będzie powiedziane, 1% związane z procesorem i 99% związane z IO.Memorystream
obiektu (którym jest Stream), przetwarzanie będzie w 100% związane z procesorem.Tak więc, jak widzisz, z punktu widzenia programisty obiektowego, chociaż zawsze uzyskuję dostęp do
Stream
obiektu, to, co dzieje się poniżej, może w dużym stopniu zależeć od ostatecznego typu obiektu.Teraz, aby zoptymalizować rzeczy, czasem warto uruchomić równolegle kod (uwaga: nie używam słowa asynchronicznego), jeśli jest to możliwe i / lub konieczne.
Kilka przykładów:
Przed asynchronizacją / oczekiwaniem mieliśmy zasadniczo dwa rozwiązania tego problemu:
Async / await to tylko wspólny model programowania, oparty na koncepcji Zadania . Jest nieco łatwiejszy w użyciu niż wątki lub pule wątków do zadań związanych z procesorem i znacznie łatwiejszy w użyciu niż stary model Begin / End. Pod przykrywką jest to jednak „po prostu” bardzo wyrafinowane opakowanie pełne funkcji na obu.
Tak więc, prawdziwa wygrana dotyczy głównie zadań związanych z IO , zadań, które nie wykorzystują procesora, ale async / Oczekiwanie jest nadal tylko modelem programowania, nie pomaga ci określić, w jaki sposób / gdzie przetwarzanie nastąpi w końcu.
Oznacza to, że nie dzieje się tak dlatego, że klasa ma metodę „DoSomethingAsync” zwracającą obiekt Task, który można założyć, że będzie związany z procesorem (co oznacza, że może być całkiem bezużyteczny , szczególnie jeśli nie ma parametru tokenu anulowania), ani powiązania IO (co oznacza, że prawdopodobnie jest to konieczne ) lub kombinacja obu (ponieważ model jest dość wirusowy, wiązanie i potencjalne korzyści mogą być ostatecznie bardzo mieszane i nie tak oczywiste).
Tak więc, wracając do moich przykładów, wykonywanie operacji zapisu przy użyciu asynchronizacji / oczekiwania na MemoryStream pozostanie związane z procesorem (prawdopodobnie nie skorzystam z niego), chociaż na pewno skorzystam z niego z plikami i strumieniami sieciowymi.
źródło
Podsumowując inne odpowiedzi:
Asynchronizacja / oczekiwanie jest tworzona przede wszystkim dla zadań powiązanych z IO, ponieważ dzięki ich użyciu można uniknąć blokowania wątku wywołującego. Ich głównym zastosowaniem są wątki interfejsu użytkownika, w których nie jest pożądane blokowanie wątku podczas operacji związanej z IO.
Async nie tworzy własnego wątku. Wątek metody wywołującej służy do wykonywania metody asynchronicznej, dopóki nie znajdzie oczekiwanego. Ten sam wątek kontynuuje wykonywanie reszty metody wywołującej poza wywołaniem metody asynchronicznej. W ramach wywoływanej metody asynchronicznej po powrocie z tego, co oczekiwane, kontynuację można wykonać na wątku z puli wątków - jedyne miejsce, w którym pojawia się osobny wątek.
źródło
Próbuję to wyjaśnić oddolnie. Może ktoś uzna to za pomocne. Byłem tam, zrobiłem to, wymyśliłem na nowo, kiedy tworzyłem proste gry w DOS-ie w Pascalu (stare dobre czasy ...)
Więc ... W aplikacji sterowanej każdym zdarzeniem znajduje się pętla zdarzeń, która wygląda mniej więcej tak:
Ramy zwykle ukrywają przed tobą ten szczegół, ale on tam jest. Funkcja getMessage odczytuje następne zdarzenie z kolejki zdarzeń lub czeka, aż zdarzenie się wydarzy: ruch myszy, klawisz dostępu, keyup, kliknięcie itp. A następnie dispatchMessage wysyła zdarzenie do odpowiedniej procedury obsługi zdarzenia. Następnie czeka na następne zdarzenie i tak dalej, aż nadejdzie zdarzenie zakończenia, które opuści pętle i zakończy aplikację.
Procedury obsługi zdarzeń powinny działać szybko, aby pętla zdarzeń mogła sondować więcej zdarzeń, a interfejs użytkownika pozostaje responsywny. Co się stanie, jeśli kliknięcie przycisku wywoła taką kosztowną operację?
Interfejs użytkownika przestaje odpowiadać do czasu zakończenia 10-sekundowej operacji, gdy kontrola pozostaje w obrębie funkcji. Aby rozwiązać ten problem, musisz podzielić zadanie na małe części, które można szybko wykonać. Oznacza to, że nie możesz obsłużyć całości w jednym wydarzeniu. Musisz wykonać niewielką część pracy, a następnie opublikować kolejne wydarzenie w kolejce zdarzeń, aby poprosić o kontynuację.
Więc zmień to na:
W takim przypadku uruchamia się tylko pierwsza iteracja, a następnie wysyła komunikat do kolejki zdarzeń, aby uruchomić następną iterację i zwraca. Nasza przykładowa
postFunctionCallMessage
pseudo funkcja umieszcza w kolejce zdarzenie „wywołaj tę funkcję”, więc dyspozytor zdarzeń wywoła je, gdy do niego dotrze. Pozwala to na przetwarzanie wszystkich innych zdarzeń GUI podczas ciągłego uruchamiania również fragmentów pracy o długim czasie pracy.Tak długo, jak długo działa to długo działające zadanie, jego kontynuacja jest zawsze w kolejce zdarzeń. Więc w zasadzie wymyśliłeś swój własny harmonogram zadań. Gdzie zdarzeniami kontynuującymi w kolejce są uruchomione „procesy”. W rzeczywistości robią to systemy operacyjne, z tym wyjątkiem, że wysyłanie zdarzeń kontynuacji i powrót do pętli harmonogramu odbywa się za pośrednictwem przerwania czasomierza CPU, w którym system operacyjny zarejestrował kod przełączania kontekstu, więc nie musisz się tym przejmować. Ale tutaj piszesz swój harmonogram, więc musisz się tym zająć - do tej pory.
Możemy więc uruchamiać długo działające zadania w jednym wątku równolegle z GUI, dzieląc je na małe części i wysyłając zdarzenia kontynuacji. To jest ogólna idea
Task
klasy. Reprezentuje kawałek pracy, a kiedy go wywołujesz.ContinueWith
, określasz, jaką funkcję wywołać jako następny kawałek, gdy bieżący kawałek się zakończy (a jego wartość zwracana jest przekazywana do kontynuacji).Task
Klasa korzysta z puli wątków, w których występuje pętla zdarzenie w każdym wątku czeka robić kawałki podobne do pracy chcą pokazałem na początku. W ten sposób możesz równolegle wykonywać miliony zadań, ale tylko kilka wątków do ich uruchomienia. Ale działałoby to równie dobrze z jednym wątkiem - pod warunkiem, że twoje zadania są odpowiednio podzielone na małe kawałki, każdy z nich wydaje się działać w trybie parellel.Ale ręczne wykonywanie całego łańcucha dzielenia pracy na małe części jest uciążliwe i całkowicie psuje układ logiki, ponieważ cały kod zadania w tle jest w zasadzie
.ContinueWith
bałaganem. Tutaj kompilator pomaga. Wszystko to łączy i kontynuuje dla Ciebie w tle. Kiedy mówiszawait
, że mówisz kompilatorowi, że „zatrzymaj się tutaj, dodaj resztę funkcji jako zadanie kontynuacji”. Kompilator zajmie się resztą, więc nie musisz.źródło
W rzeczywistości
async await
łańcuchy są maszyną stanu generowaną przez kompilator CLR.async await
używa jednak wątków, które TPL używa puli wątków do wykonywania zadań.Powodem, dla którego aplikacja nie jest blokowana, jest to, że maszyna stanu może zdecydować, która wspólna procedura ma zostać wykonana, powtórzyć, sprawdzić i ponownie podjąć decyzję.
Dalsza lektura:
Co generuje asynchronizacja i oczekiwanie?
Async Await and the Generated StateMachine
Asynchroniczne C # i F # (III.): Jak to działa? - Tomas Petricek
Edytuj :
W porządku. Wygląda na to, że moje opracowanie jest nieprawidłowe. Muszę jednak zaznaczyć, że maszyny stanowe są ważnymi zasobami dla
async await
s. Nawet jeśli weźmiesz asynchroniczne operacje we / wy, nadal potrzebujesz pomocnika, aby sprawdzić, czy operacja jest zakończona, dlatego nadal potrzebujemy maszyny stanów i ustalimy, która procedura może zostać wykonana asychronicznie razem.źródło
To nie jest bezpośrednia odpowiedź na pytanie, ale myślę, że jest to interesująca dodatkowa informacja:
Asynchronizacja i oczekiwanie same w sobie nie tworzą nowych wątków. ALE w zależności od tego, gdzie używasz asynchronicznego oczekiwania, część synchroniczna PRZED oczekiwaniem może działać w innym wątku niż część synchroniczna PO Oczekiwaniu (na przykład ASP.NET i rdzeń ASP.NET zachowują się inaczej).
W aplikacjach opartych na wątkach interfejsu użytkownika (WinForms, WPF) będziesz przed i po tym samym wątku. Ale gdy używasz asynchronizacji w wątku puli wątków, wątek przed i po oczekiwaniu może nie być taki sam.
Świetne wideo na ten temat
źródło