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.

W języku C# 5 wprowadzono uproszczone podejście, programowanie asynchroniczne, które wykorzystuje asynchroniczną obsługę w wersji .NET Framework 4.5 lub wyższej, na platformie .NET Core i w środowisku Windows Runtime. 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.

Asynchroniczność poprawia czas odpowiedzi

Synchronizacja jest niezbędna w przypadku działań, które mogą blokować, takich 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 programu .NET i środowiska Windows Runtime zawierają metody, które obsługują programowanie asynchroniczne.

Obszar aplikacji Typy .NET z metodami asynchronicznych Windows Typy środowiska uruchomieniowego z metodami 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 async i await w języku C# to centrum programowania asynchronicznego. Używając tych dwóch słów kluczowych, można użyć zasobów w programie .NET Framework, .NET Core lub środowisku Windows Runtime, aby utworzyć metodę asynchroniczną niemal tak samo łatwo, jak w przypadku tworzenia metody synchronicznej. Metody asynchroniczne, które definiujesz za pomocą słowa kluczowego , są określane async jako metody asynchroniczne.

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

Kompletny przykład interfejsu Windows Presentation Foundation (WPF) można pobrać z programowania asynchronicznego z async i await w języku C#.

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

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

    DoIndependentWork();

    string contents = await getStringTask;

    return contents.Length;
}

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

Z poprzedniego przykładu możesz poznać kilka rozwiązań. Rozpocznij od podpisu metody. Zawiera on async modyfikator . Zwracany typ to Task<int> (zobacz sekcję "Typy zwracane", aby uzyskać więcej opcji). Nazwa metody kończy się na Async . W treści metody zwraca GetStringAsync wartość Task<string> . Oznacza to, że podczas await zadania otrzymasz ( string contents ). Przed oczekiwaniem na zadanie możesz wykonać pracę, która nie polega na zadaniu string z GetStringAsync .

Zwróć szczególną uwagę na await operator . Wstrzymuje GetUrlContentLengthAsync :

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

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

Jeśli nie ma żadnej pracy, która może wykonać między wywołaniem i oczekiwaniem na jego ukończenie, możesz uprościć kod, wywołując i oczekując w GetUrlContentLengthAsync GetStringAsync poniższej pojedynczej instrukcji.

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

Poniższe charakterystyki podsumowują, co sprawia, że poprzedni przykład jest metodą asynchroniczną:

  • 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 instrukcje return, w których operand ma typ TResult .
    • Task Jeśli metoda nie ma instrukcji return lub ma instrukcji return bez operandu.
    • void w przypadku pisania procedury obsługi zdarzeń asynchronicznych.
    • Każdy inny typ, który ma GetAwaiter metodę (począwszy od języka C# 7.0).

    Aby uzyskać więcej informacji, zobacz sekcję Return types and parameters (Zwracane typy i parametry).

  • Metoda zwykle zawiera co najmniej jedno wyrażenie, które oznacza punkt, w którym metoda nie może kontynuować działania, dopóki oczekiwana operacja await asynchroniczna nie zostanie ukończona. 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 synchronizacji w poprzednich wersjach programu .NET Framework, zobacz TPL i .NET Framework programowania asynchronicznego.

Co się dzieje w metodzie asynchronicznej

Ważne jest, aby rozumieć programowanie asynchroniczne jako przepływ sterowania od metody do metody. Poniższy diagram prowadzi przez proces:

Śledzenie nawigacji przepływu sterowania asynchronicznego

Liczby na diagramie odpowiadają następującym krokom zainicjowane, gdy metoda wywołująca wywołuje metodę asynchroniczną.

  1. Metoda wywołująca wywołuje metodę i oczekuje GetUrlContentLengthAsync na metodę asynchroniczną.

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

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

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

  4. Ponieważ jeszcze nie oczekiwano, program może kontynuować pracę, która nie zależy od getStringTask GetUrlContentLengthAsync wyniku końcowego z . GetStringAsync Ta praca jest reprezentowana przez wywołanie metody synchronicznej DoIndependentWork .

  5. DoIndependentWork jest metodą synchroniczną, która robi swoje i wraca do swojego wywołującego.

  6. GetUrlContentLengthAsync zabrakło pracy, co może 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 .

    W związku GetUrlContentLengthAsync z tym używa operatora await, aby wstrzymać postęp i wywnioskować kontrolę do metody, która o nazwie GetUrlContentLengthAsync . GetUrlContentLengthAsync Zwraca do Task<int> 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 getStringTask z tym ) zakończy się przed GetUrlContentLengthAsync oczekiwanie na niego, kontrolka pozostaje w GetUrlContentLengthAsync . Koszt wstrzymania, a następnie powrotu do usługi zostanie zmarnowany, jeśli wywołany proces asynchroniczny został już ukończony i nie trzeba czekać na GetUrlContentLengthAsync getStringTask wynik GetUrlContentLengthAsync końcowy.

    Wewnątrz metody wywołującej wzorzec przetwarzania jest kontynuowany. Wywołujący może wykonać inną pracę, która nie zależy od wyniku z przed oczekiwaniem na ten wynik, lub może od razu GetUrlContentLengthAsync poczekać. Metoda wywołująca oczekuje na GetUrlContentLengthAsync , a oczekuje na GetUrlContentLengthAsync GetStringAsync .

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

  8. Gdy GetUrlContentLengthAsync ma wynik ciągu, metoda może obliczyć długość ciągu. Następnie działanie polecenia GetUrlContentLengthAsync również zostanie ukończone i program obsługi zdarzeń oczekiwania może zostać wznowiony. 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źć takie GetStringAsync metody, jak te, które obsługują programowanie asynchroniczne. .NET Framework 4.5 lub wyższej, a program .NET Core zawiera wiele elementów członkowskich, które pracują z async usługami i await . Można je rozpoznać za pomocą sufiksu "Async", który jest dołączany do nazwy składowej, oraz przez zwracany typ Task lub Task<TResult> . Na przykład klasa zawiera metody takie jak System.IO.Stream , i obok metod CopyToAsync ReadAsync WriteAsync synchronicznych CopyTo , i Read Write .

Środowisko Windows Runtime zawiera również wiele metod, których można używać z Windows async await aplikacjami. Aby uzyskać więcej informacji, zobacz Threading and async programming for UWP development,and Asynchronous programming (Windows Store apps) (Tworzenie wątków i programowanie asynchroniczne na platformie UWP) oraz Szybki start: wywoływanie asynchronicznych interfejsów API w języku C# lub Visual Basic w przypadku używania wcześniejszych wersji środowiska Windows Runtime.

Wątków

Metody async mają być operacjami niepowodującymi blokowania. Wyrażenie w metodzie asynchronicznej nie blokuje bieżącego wątku, gdy await oczekujące 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 await kluczowe i nie powodują utworzenia 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. Za pomocą funkcji można przenieść pracę powiązaną z procesorem CPU do wątku w tle, ale wątek w tle nie pomoże w procesie, który po prostu oczekuje na dostęp Task.Run do wyników.

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

async i await

Jeśli określisz, że metoda jest metodą asynchroniczną przy użyciu modyfikatora asynchronicznego, należy włączyć następujące dwie możliwości.

  • Oznaczona metoda asynchroniczna może używać funkcji await do wyznaczania punktów zawieszenia. Operator informuje kompilator, że metoda asynchroniczna nie może kontynuować pracy po tym punkcie, dopóki oczekiwanie na ukończenie await procesu asynchronicznego nie zostanie ukończone. W międzyczasie sterowanie powraca do obiektu wywołującego metodę async.

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

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

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

async i await to kontekstowe słowa kluczowe. Aby uzyskać więcej informacji i przykładów, zobacz następujący temat:

Zwracane typy i parametry

Metoda asynchroniczna zwykle zwraca wartość Task lub Task<TResult> . Wewnątrz metody asynchronicznej operator jest stosowany do zadania, które jest zwracane z wywołania await innej metody asynchronicznej.

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

Typ zwracany jest typu , jeśli metoda nie ma instrukcji return lub ma instrukcje Task return, które nie zwracają operandu.

Począwszy od języka C# 7.0, można również określić inny typ zwracany, pod warunkiem, że typ zawiera GetAwaiter metodę . ValueTask<TResult> jest przykładem takiego typu. Jest on dostępny w pakiecie systemowym System.Threading.Tasks.Extension NuGet pakiet.

W poniższym przykładzie pokazano sposób deklarowania i wywołania metody, która zwraca Task<TResult> metodę 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 asynchroniczna może również mieć void zwracany typ. Ten zwracany typ jest używany głównie do definiowania programów obsługi zdarzeń, gdzie void zwracany typ jest wymagany. Programy async obsługi zdarzeń często służą jako punkt wejścia dla programów async.

Nie można czekać na metodę asynchroniczną, która ma zwracany typ, a element wywołujący metodę zwracaną przez void nie może przechwycić żadnych wyjątków zgłaszanych przez void metodę.

Metoda asynchroniczna nie może deklarować w parametrach typu , ref ani out, ale metoda może wywołać metody, które mają takie parametry. Podobnie metoda asynchroniczna nie może zwrócić wartości przez odwołanie, chociaż może wywołać metody z wartościami zwracanych ref.

Aby uzyskać więcej informacji i przykładów, zobacz Asynchroniczne typy zwracane (C#). Aby uzyskać więcej informacji na temat sposobu przechwytowania wyjątków w metodach asynchronicznych, zobacz try-catch.

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

Konwencja nazewnictwa

Zgodnie z konwencją metody, które zwracają typy często oczekiwane (na przykład , , , ) powinny mieć nazwy, które kończą się Task Task<T> na ValueTask ValueTask<T> "Async". Metody, które uruchamiają operację asynchroniczną, ale nie zwracają typu oczekiwanego, nie powinny mieć nazw, które kończą się na "Async", ale mogą zaczynać się od "Begin", "Start" lub innego czasownika 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 .

Tytuł Opis Przykład
Równoległe żądania internetowe przy użyciu elementów async i await (C#) Ilustruje, jak uruchomić kilka zadań w tym samym czasie. Przykład asynchroniczny: równoległe żądania internetowe
Asynchroniczne typy zwracane (C#) Ilustruje typy, które mogą zwracać metody asynchroniczne, i wyjaśnia, kiedy poszczególne typy są odpowiednie.
Anulowanie zadań za pomocą tokenu anulowania jako mechanizmu sygnalizowania. 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 zadań asynchronicznych podczas ich ukończenia (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 Task typach Task<TResult> i .
Asynchroniczne wideo w kanale Channel 9 Oferuje łącza do różnych plików wideo dotyczących programowania asynchronicznego.

Zobacz też