Myślałem, że to w zasadzie to samo - pisanie programów, które dzielą zadania między procesory (na komputerach z procesorami 2+). Następnie czytam to , co mówi:
Metody asynchroniczne mają być operacjami nieblokującymi. Wyrażenie oczekujące w metodzie asynchronicznej nie blokuje bieżącego wątku, gdy oczekiwane zadanie jest uruchomione. Zamiast tego wyrażenie podpisuje resztę metody jako kontynuację i zwraca kontrolę do obiektu wywołującego metodę asynchroniczną.
Słowa kluczowe asynchroniczne i oczekujące nie powodują utworzenia dodatkowych wątków. Metody asynchroniczne nie wymagają wielowątkowości, ponieważ metoda asynchroniczna nie działa na własnym wątku. Metoda działa w bieżącym kontekście synchronizacji i wykorzystuje czas w wątku tylko wtedy, gdy metoda jest aktywna. Możesz użyć Task.Run, aby przenieść pracę związaną z CPU do wątku w tle, ale wątek w tle nie pomaga w procesie, który tylko czeka na wyniki.
i zastanawiam się, czy ktoś może dla mnie przetłumaczyć to na angielski. Wydaje się, że wprowadza rozróżnienie między asynchronicznością (czy to słowo?) A wątkami i sugeruje, że możesz mieć program, który ma zadania asynchroniczne, ale nie ma wielowątkowości.
Teraz rozumiem ideę zadań asynchronicznych, takich jak przykład na pg. 467 Jona Skeeta C # In Depth, wydanie trzecie
async void DisplayWebsiteLength ( object sender, EventArgs e )
{
label.Text = "Fetching ...";
using ( HttpClient client = new HttpClient() )
{
Task<string> task = client.GetStringAsync("http://csharpindepth.com");
string text = await task;
label.Text = text.Length.ToString();
}
}
Słowo async
kluczowe oznacza „ Ta funkcja, ilekroć jest wywoływana, nie będzie wywoływana w kontekście, w którym jej zakończenie jest wymagane do wywołania wszystkiego po wywołaniu”.
Innymi słowy, pisanie go w trakcie jakiegoś zadania
int x = 5;
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);
, Ponieważ DisplayWebsiteLength()
nie ma nic wspólnego z x
lub y
spowoduje DisplayWebsiteLength()
być realizowane „w tle”, jak
processor 1 | processor 2
-------------------------------------------------------------------
int x = 5; | DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0); |
Oczywiście to głupi przykład, ale czy mam rację, czy jestem całkowicie zdezorientowany, czy co?
(Poza tym jestem zdezorientowany, dlaczego sender
i e
nigdy nie są używane w treści powyższej funkcji.)
źródło
sender
ie
sugerują, że jest to w rzeczywistości moduł obsługi zdarzeń - właściwie jedyne miejsce, w którymasync void
jest to pożądane. Najprawdopodobniej jest to wywoływane kliknięciem przycisku lub czymś podobnym - w wyniku tego działanie to dzieje się całkowicie asynchronicznie w stosunku do reszty aplikacji. Ale wciąż jest w jednym wątku - wątku interfejsu użytkownika (z niewielkim odcinkiem czasu w wątku IOCP, który wysyła wywołanie zwrotne do wątku interfejsu użytkownika).DisplayWebsiteLength
przykładowym kodzie: Nie powinieneś używaćHttpClient
wusing
instrukcji - Pod dużym obciążeniem kod może wyczerpać dostępną liczbę gniazd, co powoduje błędy SocketException. Więcej informacji na temat niewłaściwej instancji .Odpowiedzi:
Twoje nieporozumienie jest niezwykle powszechne. Wiele osób uczy się, że wielowątkowość i asynchronia są tym samym, ale tak nie jest.
Analogia zwykle pomaga. Gotujesz w restauracji. Nadchodzi zamówienie na jajka i tosty.
Czy to ma sens, że wielowątkowość to tylko jeden rodzaj asynchronii? Wątki dotyczą pracowników; asynchronia dotyczy zadań . W wielowątkowych przepływach pracy przypisujesz zadania pracownikom. W asynchronicznych jedno-wątkowych przepływach pracy masz wykres zadań, w którym niektóre zadania zależą od wyników innych; po zakończeniu każdego zadania wywołuje kod, który planuje następne zadanie, które można uruchomić, biorąc pod uwagę wyniki właśnie ukończonego zadania. Ale (miejmy nadzieję) potrzebujesz tylko jednego pracownika do wykonania wszystkich zadań, a nie jednego pracownika na zadanie.
Pomoże to zrozumieć, że wiele zadań nie jest związanych z procesorem. W przypadku zadań związanych z procesorem sensowne jest zatrudnienie tylu pracowników (wątków), ile jest procesorów, przypisanie jednego zadania do każdego pracownika, przypisanie jednego procesora do każdego pracownika i zlecenie każdemu procesorowi wykonywania innych zadań, jak tylko obliczenie wyniku jako tak szybko jak to możliwe. Ale w przypadku zadań, które nie oczekują na procesorze, nie trzeba wcale przypisywać pracownika. Po prostu czekasz na wiadomość, że wynik jest dostępny, i robisz coś innego, czekając . Gdy ta wiadomość dotrze, możesz zaplanować kontynuację ukończonego zadania jako kolejną rzecz na liście spraw do sprawdzenia.
Spójrzmy więc bardziej szczegółowo na przykład Jona. Co się dzieje?
text
i uruchomić resztę metody.To tak jak w mojej analogii. Ktoś prosi o dokument. Wysyłasz dokument pocztą i kontynuujesz wykonywanie innych prac. Kiedy nadejdzie na pocztę, zostaniesz zasygnalizowany, a gdy masz na to ochotę, wykonasz resztę przepływu pracy - otwórz kopertę, opłać opłaty za dostawę, cokolwiek. Nie musisz zatrudniać innego pracownika, aby zrobić to wszystko za Ciebie.
źródło
JavaScript w przeglądarce jest doskonałym przykładem programu asynchronicznego, który nie ma wątków.
Nie musisz się martwić, że wiele fragmentów kodu dotyka jednocześnie tych samych obiektów: każda funkcja zakończy działanie, zanim jakikolwiek inny skrypt javascript zostanie dopuszczony do działania na stronie.
Jednak podczas wykonywania czegoś takiego jak żądanie AJAX żaden kod nie jest w ogóle uruchomiony, więc inny skrypt javascript może reagować na takie zdarzenia, jak kliknięcia, dopóki żądanie nie wróci i nie wywoła powiązanego z nim wywołania zwrotnego. Jeśli jeden z tych innych programów obsługi zdarzeń nadal działa, gdy żądanie AJAX powróci, jego moduł obsługi nie zostanie wywołany, dopóki nie zostanie zakończony. Działa tylko jeden „wątek” JavaScript, mimo że możliwe jest skuteczne wstrzymanie wykonywanej czynności do momentu uzyskania potrzebnych informacji.
W aplikacjach C # to samo dzieje się za każdym razem, gdy masz do czynienia z elementami interfejsu użytkownika - możesz wchodzić w interakcje z elementami interfejsu użytkownika tylko w wątku interfejsu użytkownika. Jeśli użytkownik kliknął przycisk, a użytkownik chciał odpowiedzieć czytając duży plik z dysku, niedoświadczony programista może popełnić błąd odczytu pliku w samej procedurze obsługi zdarzenia kliknięcia, co spowodowałoby „zawieszenie się” aplikacji do momentu plik zakończył ładowanie, ponieważ nie można reagować na żadne kliknięcia, najechanie kursorem ani inne zdarzenia związane z interfejsem użytkownika, dopóki ten wątek nie zostanie zwolniony.
Jedną z opcji, której programiści mogą użyć, aby uniknąć tego problemu, jest utworzenie nowego wątku w celu załadowania pliku, a następnie poinformowanie kodu tego wątku, że po załadowaniu pliku musi ponownie uruchomić pozostały kod w wątku interfejsu użytkownika, aby mógł zaktualizować elementy interfejsu użytkownika na podstawie tego, co znalazł w pliku. Do niedawna takie podejście było bardzo popularne, ponieważ ułatwiało to biblioteki i język C #, ale jest zasadniczo bardziej skomplikowane, niż musi być.
Jeśli myślisz o tym, co robi procesor, gdy czyta plik na poziomie sprzętu i systemu operacyjnego, to w zasadzie wydaje polecenie odczytania fragmentów danych z dysku do pamięci i uderzenia w system operacyjny z „przerwaniem” „po zakończeniu odczytu. Innymi słowy, czytanie z dysku (lub dowolnego wejścia / wyjścia naprawdę) jest z natury asynchroniczną operacją. Koncepcja wątku oczekującego na zakończenie operacji we / wy jest abstrakcją, którą twórcy bibliotek stworzyli, aby ułatwić programowanie. To nie jest konieczne.
Teraz większość operacji we / wy w .NET ma odpowiednią
...Async()
metodę, którą można wywołać, która zwracaTask
prawie natychmiast. Możesz do tego dodać wywołania zwrotne, abyTask
określić kod, który chcesz uruchomić po zakończeniu operacji asynchronicznej. Możesz także określić, w którym wątku chcesz uruchomić ten kod, i możesz podać token, który operacja asynchroniczna może od czasu do czasu sprawdzać, aby sprawdzić, czy zdecydowano się anulować zadanie asynchroniczne, co daje mu możliwość szybkiego zatrzymania pracy i z wdziękiem.Do czasu
async/await
dodania słów kluczowych C # był znacznie bardziej oczywisty na temat sposobu wywoływania kodu wywołania zwrotnego, ponieważ te wywołania zwrotne były w formie delegatów powiązanych z zadaniem. Aby nadal korzystać z...Async()
operacji, unikając złożoności kodu,async/await
abstrahuje od tworzenia tych delegatów. Ale wciąż są tam w skompilowanym kodzie.Dzięki temu moduł obsługi zdarzeń interfejsu użytkownika może mieć
await
operację we / wy, zwalniając wątek interfejsu użytkownika do wykonywania innych czynności i mniej więcej automatycznie wracając do wątku interfejsu użytkownika po zakończeniu odczytu pliku - bez konieczności utwórz nowy wątek.źródło