Udostępnij za pośrednictwem


Model programowania asynchronicznego zadań (APM)

Możesz uniknąć problemów z wydajnością i poprawić ogólny czas odpowiedzi aplikacji, stosując programowanie asynchroniczne. Jednak tradycyjne techniki pisania aplikacji asynchronicznych mogą być skomplikowane, przez co trudne do pisania, debugowania i konserwacji.

Język C# obsługuje uproszczone podejście, programowanie asynchroniczne, które korzysta z asynchronicznej obsługi w środowisku uruchomieniowym platformy .NET. Kompilator wykonuje trudną pracę, którą kiedyś wykonywał programista, a aplikacja zachowuje strukturę logiczną przypominającą kod synchroniczny. W efekcie masz wszystkie korzyści wynikające z programowania asynchronicznego przy ułamkowym nakładzie pracy.

Ten temat zawiera omówienie, kiedy i jak stosować programowanie async oraz zawiera łącza do tematów zawierających szczegółowe informacje oraz przykłady.

Asynchronizacja poprawia czas odpowiedzi

Asynchronia jest niezbędna w przypadku działań, które potencjalnie blokują, takie jak dostęp do internetu. Dostęp do zasobów sieci Web bywa powolny lub opóźniony. Jeśli takie działanie zostanie zablokowane w procesie synchronicznym, cała aplikacja musi czekać. W procesie asynchronicznym aplikacja może kontynuować wykonywanie innych zadań, które nie są zależne od zasobów sieci Web, do momentu zakończeniem zadania mogącego powodować blokowanie.

W poniższej tabeli przedstawiono typowe obszary, w których programowanie asynchroniczne poprawia czas odpowiedzi. Wymienione interfejsy API z platformy .NET i środowisko wykonawcze systemu Windows zawierają metody obsługujące programowanie asynchroniczne.

Obszar aplikacji Typy platformy .NET z metodami asynchronizowymi środowisko wykonawcze systemu Windows typów za pomocą metod asynchronicznych
Web access HttpClient Windows.Web.Http.HttpClient
SyndicationClient
Praca z plikami JsonSerializer
StreamReader
StreamWriter
XmlReader
XmlWriter
StorageFile
Praca z obrazami MediaCapture
BitmapEncoder
BitmapDecoder
Programowanie WCF Operacje synchroniczne i asynchroniczne

Asynchroniczność okazuje się szczególnie cenna w przypadku aplikacji, które mają dostęp do wątku interfejsu użytkownika, ponieważ wszystkie działania związane z interfejsem użytkownika korzystają zazwyczaj z jednego wątku. Jeśli w aplikacji synchronicznej jest zablokowany jakikolwiek proces, zablokowane są wszystkie procesy. Aplikacja przestanie odpowiadać i możesz dojść do wniosku, że uległa awarii, a tymczasem może po prostu być w stanie oczekiwania.

Kiedy używasz metod asynchronicznych, aplikacja dalej odpowiada interfejsowi użytkownika. Możesz przykładowo zmienić rozmiar lub zminimalizować aplikację lub możesz ją zamknąć, jeżeli nie chcesz czekać aż zakończy zadanie.

Podejście async oferuje również odpowiednik automatycznego przejścia do listy opcji do wybrania przy projektowaniu operacji asynchronicznych. Oznacza to, że można korzystać z wszystkich zalet tradycyjnego programowania asynchronicznego, ale przy znacznie mniejszym nakładzie pracy programisty.

Metody asynchroniczne są łatwe do zapisu

Słowa kluczowe asynchroniczne i await w języku C# są sercem programowania asynchronicznego. Używając tych dwóch słów kluczowych, możesz użyć zasobów w programie .NET Framework, .NET Core lub środowisko wykonawcze systemu Windows, aby utworzyć metodę asynchroniczną niemal tak łatwo, jak tworzysz metodę synchroniczną. Metody asynchroniczne definiowane za pomocą słowa kluczowego async są określane jako metody asynchroniczne.

W poniższym przykładzie przedstawiono metodę async. Prawie wszystko w kodzie powinno wyglądać znajomo.

Kompletny przykład programu Windows Presentation Foundation (WPF) jest dostępny do pobrania z programowania asynchronicznego z funkcją asynchroniczną i await w języku C#.

public async Task<int> GetUrlContentLengthAsync()
{
    using var client = new HttpClient();

    Task<string> getStringTask =
        client.GetStringAsync("https://learn.microsoft.com/dotnet");

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

void DoIndependentWork()
{
    Console.WriteLine("Working...");
}

Z poprzedniego przykładu możesz zapoznać się z kilkoma rozwiązaniami. Zacznij od podpisu metody. async Zawiera modyfikator. Zwracany typ to Task<int> (zobacz sekcję "Zwracane typy", aby uzyskać więcej opcji). Nazwa metody kończy się na .Async W treści metody GetStringAsync zwraca wartość Task<string>. Oznacza to, że gdy await otrzymasz zadanie string (contents). Przed oczekiwaniem na zadanie możesz wykonać pracę, która nie polega na string obiekcie z .GetStringAsync

Zwróć szczególną await uwagę na operatora. GetUrlContentLengthAsyncZawiesza się :

  • GetUrlContentLengthAsync Nie można kontynuować, dopóki getStringTask nie zostanie ukończona.
  • W międzyczasie kontrolka powraca do obiektu wywołującego .GetUrlContentLengthAsync
  • Kontrolka zostanie wznowiona w tym miejscu po getStringTask zakończeniu.
  • Następnie await operator pobiera string wynik z .getStringTask

Instrukcja return określa wynik liczby całkowitej. Wszystkie metody oczekujące GetUrlContentLengthAsync na pobranie wartości długości.

Jeśli GetUrlContentLengthAsync nie ma żadnej pracy, którą może wykonać między wywołaniem GetStringAsync i oczekiwaniem na jego ukończenie, możesz uprościć kod, wywołując i oczekując na następującą pojedynczą instrukcję.

string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");

Poniżej przedstawiono podsumowanie tego, co sprawia, że w poprzednim przykładzie metoda asynchronizuj:

  • Podpis metody zawiera async modyfikator.

  • Nazwa metody async kończy się zwyczajowo sufiksem „Async”.

  • Zwracany typ może być jednym z następujących:

    • Task<TResult> jeśli metoda ma instrukcję return, w której operand ma typ TResult.
    • Task jeśli metoda nie ma instrukcji return lub ma instrukcję return bez operandu.
    • void jeśli piszesz asynchroniczny program obsługi zdarzeń.
    • Każdy inny typ, który ma metodę GetAwaiter .

    Aby uzyskać więcej informacji, zobacz sekcję Zwracane typy i parametry .

  • Metoda zwykle zawiera co najmniej jedno await wyrażenie, które oznacza punkt, w którym metoda nie może kontynuować, dopóki nie zostanie ukończona oczekiwana operacja asynchroniczna. W tym czasie metoda jest zawieszona, a sterowanie powraca do obiektu wywołującego metodę. Następna sekcji tego tematu przedstawia, co się dzieje w punkcie zawieszenia.

W metodzie asynchronicznej używasz podanych słów kluczowych i typów w celu wskazania, co chcesz zrobić, a kompilator zajmie się resztą, w tym śledzeniem tego, co musi się zdarzyć, gdy sterowanie powraca do punktu oczekiwania w metodzie zawieszonej. Niektóre procesy, takie jak pętle i obsługa wyjątków, mogą być trudne do obsłużenia w tradycyjnym kodzie asynchronicznym. W metodzie asynchronicznej wpisujesz te elementy podobnie jak w rozwiązaniu synchronicznym i problem rozwiązany.

Aby uzyskać więcej informacji na temat asynchronii w poprzednich wersjach programu .NET Framework, zobacz TPL i tradycyjne programowanie asynchroniczne programu .NET Framework.

Co się dzieje w metodzie asynchronicznej

Ważne jest, aby rozumieć programowanie asynchroniczne jako przepływ sterowania od metody do metody. Na poniższym diagramie przedstawiono proces:

Trace navigation of async control flow

Liczby na diagramie odpowiadają poniższym krokom zainicjowanym, gdy metoda wywołująca wywołuje metodę asynchroniową.

  1. Wywołanie metody wywołującej i oczekiwanie na metodę asynchroniową GetUrlContentLengthAsync .

  2. GetUrlContentLengthAsyncTworzy wystąpienie i wywołuje GetStringAsync metodę HttpClient asynchroniczną w celu pobrania zawartości witryny internetowej jako ciągu.

  3. Coś się dzieje w GetStringAsync tym, że zawiesza swój postęp. Być może metoda musi czekać na pobranie strony internetowej lub inne działanie blokujące. Aby uniknąć blokowania zasobów, GetStringAsync daje kontrolę nad obiektem wywołującym, GetUrlContentLengthAsync.

    GetStringAsync Zwraca wartość Task<TResult>, gdzie TResult jest ciągiem i GetUrlContentLengthAsync przypisuje zadanie do zmiennej getStringTask . Zadanie reprezentuje ciągły proces wywołania metody GetStringAsync, z zobowiązaniem do utworzenia rzeczywistej wartości ciągu po zakończeniu pracy.

  4. Ponieważ getStringTask jeszcze nie oczekiwano, może kontynuować inne prace, GetUrlContentLengthAsync które nie zależą od końcowego wyniku z .GetStringAsync Ta praca jest reprezentowana przez wywołanie metody DoIndependentWorksynchronicznej .

  5. DoIndependentWork jest metodą synchroniczną, która wykonuje swoją pracę i wraca do elementu wywołującego.

  6. GetUrlContentLengthAsynczabrakło pracy, którą można zrobić bez wyniku z .getStringTask GetUrlContentLengthAsync Następnie chce obliczyć i zwrócić długość pobranego ciągu, ale metoda nie może obliczyć tej wartości, dopóki metoda nie ma ciągu.

    GetUrlContentLengthAsync W związku z tym używa operatora await, aby zawiesić jego postęp i kontrolować metodę o nazwie GetUrlContentLengthAsync. GetUrlContentLengthAsync metoda zwraca element Task<int> do elementu wywołującego. Zadanie przedstawia obietnicę utworzenia w wyniku liczby całkowitej, która jest długością pobranego ciągu.

    Uwaga

    Jeśli GetStringAsync (i w związku z tym getStringTask) zostanie ukończony przed jego oczekiwaniemGetUrlContentLengthAsync, kontrolka pozostanie w .GetUrlContentLengthAsync Koszt wstrzymania, a następnie powrotu GetUrlContentLengthAsync do zostanie zmarnowany, jeśli wywołany proces getStringTask asynchroniczny został już zakończony i GetUrlContentLengthAsync nie musi czekać na końcowy wynik.

    Wewnątrz metody wywołującej wzorzec przetwarzania jest kontynuowany. Obiekt wywołujący może wykonać inną pracę, która nie zależy od wyniku przed GetUrlContentLengthAsync oczekiwaniem na ten wynik, lub obiekt wywołujący może oczekiwać natychmiast. Metoda wywołująca czeka na GetUrlContentLengthAsyncelement i GetUrlContentLengthAsync czeka na GetStringAsync.

  7. GetStringAsync kończy i generuje wynik ciągu. Wynik ciągu nie jest zwracany przez wywołanie metody GetStringAsync w oczekiwany sposób. (Pamiętaj, że metoda zwróciła już zadanie w kroku 3). Zamiast tego wynik ciągu jest przechowywany w zadaniu reprezentującym ukończenie metody getStringTask. Operator await pobiera wynik z elementu getStringTask. Instrukcja przypisania przypisuje pobrany wynik do contents.

  8. Gdy GetUrlContentLengthAsync wynik ciągu, metoda może obliczyć długość ciągu. Następnie praca programu GetUrlContentLengthAsync jest również zakończona, a program obsługi zdarzeń oczekiwania może wznawiać. W pełnym przykładzie na końcu tematu można zobaczyć, że program obsługi zdarzeń pobiera i drukuje wynikową wartość długości. Jeśli dopiero zaczynasz przygodę z programowaniem asynchronicznym, zastanów się przez chwilę, jaka jest różnica między zachowaniem synchronicznym i asynchronicznym. Metoda synchroniczna kończy działanie, gdy praca jest zakończona (krok 5), natomiast metoda asynchroniczna zwraca wartość zadania, gdy jej praca jest zawieszona (kroki 3 i 6). Gdy metoda async ukończy pracę, zadanie jest oznaczane jako ukończone, a wynik, o ile istnieje, jest zapisywany w zadaniu.

Metody asynchroniczne interfejsu API

Być może zastanawiasz się, gdzie znaleźć metody, takie jak GetStringAsync obsługa programowania asynchronicznego. Program .NET Framework 4.5 lub nowszy i platforma .NET Core zawierają wiele elementów członkowskich, z którymi współpracuje program async i await. Można je rozpoznać za pomocą sufiksu "Async", który jest dołączany do nazwy elementu członkowskiego, oraz przez ich zwracany typ Task lub Task<TResult>. Na przykład System.IO.Stream klasa zawiera metody, takie jak CopyToAsync, ReadAsynci WriteAsync obok metod CopyTosynchronicznych , Readi Write.

Środowisko wykonawcze systemu Windows zawiera również wiele metod, których można używać z aplikacjami async systemu Windows i await . Aby uzyskać więcej informacji, zobacz Tworzenie wątków i programowanie asynchroniczne dla platformy UWP oraz Programowanie asynchroniczne (aplikacje ze Sklepu Windows) i Szybki start: wywoływanie asynchronicznych interfejsów API w języku C# lub Visual Basic, jeśli używasz wcześniejszych wersji środowisko wykonawcze systemu Windows.

Wątki

Metody async mają być operacjami niepowodującymi blokowania. Wyrażenie await w metodzie asynchronicznej nie blokuje bieżącego wątku, gdy oczekiwane zadanie jest uruchomione. Zamiast tego, wyrażenie rejestruje pozostałą część metody jako kontynuację i przekazuje sterowanie do obiektu wywołującego metody async.

Słowa async kluczowe i await nie powodują tworzenia dodatkowych wątków. Metody komunikacji async nie wymagają wielowątkowości, ponieważ nie działają we własnym wątku. Metoda działa w bieżącym kontekście synchronizacji i używa czasu wątku, tylko wtedy, gdy jest aktywna. Możesz użyć Task.Run polecenia , aby przenieść pracę związaną z procesorem CPU do wątku w tle, ale wątek w tle nie pomaga w procesie, który po prostu czeka na udostępnienie wyników.

Podejście async do programowania asynchronicznego jest preferowane prawie w każdym przypadku. W szczególności takie podejście jest lepsze niż BackgroundWorker klasa operacji związanych z operacjami we/wy, ponieważ kod jest prostszy i nie musisz chronić się przed warunkami wyścigu. W połączeniu Task.Run z metodą programowanie asynchroniczne jest lepsze niż BackgroundWorker w przypadku operacji związanych z procesorem CPU, ponieważ programowanie asynchroniczne oddziela szczegóły koordynacji uruchamiania kodu od pracy, która Task.Run przenosi się do puli wątków.

async i await

Jeśli określisz, że metoda jest metodą asynchronizującą przy użyciu modyfikatora asynchronicznego, włącz następujące dwie możliwości.

  • Oznaczona metoda asynchronizna może służyć do wyznaczania punktów zawieszenia. Operator await informuje kompilator, że metoda asynchroniczna nie może kontynuować przeszłości tego punktu, dopóki nie zostanie ukończony oczekiwany proces asynchroniczny. W międzyczasie sterowanie powraca do obiektu wywołującego metodę async.

    Zawieszenie metody asynchronicznej w wyrażeniu await nie stanowi wyjścia z metody, a finally bloki nie są uruchamiane.

  • Metoda oznaczona jako async sama może być oczekiwana przez metody, które ją wywołują.

Metoda asynchronicznie zwykle zawiera jedno lub więcej wystąpień await operatora, ale brak await wyrażeń nie powoduje błędu kompilatora. Jeśli metoda asynchroniczna nie używa await operatora do oznaczania punktu zawieszenia, metoda jest wykonywana jako metoda synchroniczna, pomimo async modyfikatora. Kompilator generuje ostrzeżenia dla takich metod.

async i await są słowami kluczowymi kontekstowymi. Aby uzyskać więcej informacji i przykładów, zobacz następujący temat:

Zwracane typy i parametry

Metoda asynchronicznie zwykle zwraca wartość Task lub Task<TResult>. Wewnątrz metody await asynchronicznej operator jest stosowany do zadania zwróconego z wywołania do innej metody asynchronicznej.

Typ zwracany określa Task<TResult> się, jeśli metoda zawiera instrukcję return określającą operand typu TResult.

Jest używany Task jako typ zwracany, jeśli metoda nie ma instrukcji return lub ma instrukcję return, która nie zwraca operandu.

Można również określić dowolny inny typ zwracany, pod warunkiem, że typ zawiera metodę GetAwaiter . ValueTask<TResult> jest przykładem takiego typu. Jest on dostępny w pakiecie NuGet System.Threading.Tasks.Extension .

W poniższym przykładzie pokazano, jak zadeklarować i wywołać metodę zwracającą Task<TResult> obiekt lub Task:

async Task<int> GetTaskOfTResultAsync()
{
    int hours = 0;
    await Task.Delay(0);

    return hours;
}


Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();

async Task GetTaskAsync()
{
    await Task.Delay(0);
    // No return statement needed
}

Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();

Każde zwracane zadanie reprezentuje zadanie pracy w toku. Zadanie zawiera informacje o stanie procesu asynchronicznego i, ostatecznie, albo ostatecznego wyniku procesu lub wyjątku, który zgłasza proces, jeśli się nie powiedzie.

Metoda asynchronizna może również mieć typ zwracany void . Ten typ zwracany jest używany głównie do definiowania procedur obsługi zdarzeń, w których wymagany jest zwracany void typ. Programy async obsługi zdarzeń często służą jako punkt wejścia dla programów async.

Nie można oczekiwać metody asynchronicznej, która ma void typ zwracany, a obiekt wywołujący metody zwracanej przez pustkę nie może przechwytywać żadnych wyjątków zgłaszanych przez metodę.

Metoda asynchronizuj nie może zadeklarować parametrów w parametrach ref lub out , ale metoda może wywoływać metody, które mają takie parametry. Podobnie metoda asynchronicznie nie może zwrócić wartości według odwołania, chociaż może wywołać metody z wartościami zwracanymi ref.

Aby uzyskać więcej informacji i przykładów, zobacz Async return types (C#)( Typy zwracane asynchroniczne (C#).

Asynchroniczne interfejsy API w programowaniu środowisko wykonawcze systemu Windows mają jeden z następujących typów zwracanych, które są podobne do zadań:

Konwencja nazewnictwa

Zgodnie z konwencją metody, które zwracają często oczekiwane typy (na przykład Task, , ValueTaskTask<T>, ValueTask<T>) powinny mieć nazwy kończące się ciągiem "Async". Metody, które rozpoczynają operację asynchroniczną, ale nie zwracają oczekiwanego typu, nie powinny mieć nazw kończących się ciągiem "Async", ale mogą zaczynać się od "Begin", "Start" lub innego zlecenia sugerującego, że ta metoda nie zwraca ani nie zgłasza wyniku operacji.

Można zignorować konwencję, gdy zdarzenie, klasa bazowa lub kontrakt interfejsu sugeruje inną nazwę. Na przykład nie należy zmieniać nazw typowych programów obsługi zdarzeń, takich jak OnButtonClick.

Powiązane artykuły (Visual Studio)

Nazwa opis
Równoległe tworzenie wielu żądań internetowych przy użyciu asynchronicznego i await (C#) Ilustruje, jak uruchomić kilka zadań w tym samym czasie.
Typy zwracane asynchroniczne (C#) Ilustruje typy, które mogą zwracać metody asynchroniczne, i wyjaśnia, kiedy każdy typ jest odpowiedni.
Anulowanie zadań przy użyciu tokenu anulowania jako mechanizmu sygnalizacyjnego. Przedstawia, w jaki sposób dodać następujące funkcje do rozwiązania async:

- Anulowanie listy zadań (C#)
- Anulowanie zadań po upływie czasu (C#)
- Przetwarzanie zadania asynchronicznego podczas ich wykonywania (C#)
Używanie asynchronicznego dostępu do plików (C#) Wyświetla listę korzyści wynikających ze stosowania słów kluczowych async i await przy uzyskiwaniu dostępu do plików.
Wzorzec asynchroniczny oparty na zadaniach (TAP) Opisuje wzorzec asynchroniczny, wzorzec jest oparty na typach Task i Task<TResult> .
Asynchroniczne filmy wideo w witrynie Channel 9 Oferuje łącza do różnych plików wideo dotyczących programowania asynchronicznego.

Zobacz też