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:
public async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before awaiting");
await GetSomethingAsync();
Console.WriteLine("after awaiting");
}
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:
public void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("before waiting");
DoSomethingThatTakes2Seconds();
Console.WriteLine("after waiting");
}
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:
- Cały kod prowadzący do
await
, w tym połączenie zGetSomethingAsync
- Cały następujący kod
await
Ilustracja:
code... code... code... await X(); ... code... code... code...
Przeorganizowano:
code... code... code... var x = X(); await X; code... code... code...
^ ^ ^ ^
+---- portion 1 -------------------+ +---- portion 2 ------+
Zasadniczo metoda działa w następujący sposób:
- Wykonuje wszystko do
await
Wywołuje GetSomethingAsync
metodę, która działa, i zwraca coś, co zakończy się w 2 sekundy w przyszłości
Do 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 bardzo
To 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ę.
- 2 sekundy później to, na co czekamy, kończy się i teraz dzieje się tak, że (cóż, kontekst synchronizacji) umieszcza komunikat w kolejce, na którą patrzy pętla komunikatów, mówiąc: „Hej, mam trochę więcej kodu you to execute ”, a ten kod to cały kod po oczekiwaniu.
- Gdy pętla komunikatów dotrze do tej wiadomości, po prostu „ponownie wejdzie” do tej metody, w której została przerwana, zaraz po niej
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 bez async/await
prawidłowego użycia , ponownie zablokuje pętlę komunikatów
Pod 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:
- Żądania sieciowe (i wiele innych rzeczy związanych z siecią, które wymagają czasu)
- Asynchroniczne odczytywanie i zapisywanie plików
- i wiele innych, jest to dobry znak, jeśli klasa / interfejs w pytaniu został nazwany metody
SomethingSomethingAsync
lub BeginSomething
a EndSomething
i nie jest to IAsyncResult
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.