Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
W .NET wzorzec asynchroniczny oparty na zadaniach jest zalecanym asynchronicznym wzorcem projektowania dla nowego programowania. Jest on oparty na typach Task i Task<TResult> w System.Threading.Tasks przestrzeni nazw, które reprezentują operacje asynchroniczne.
Nazewnictwo, parametry i zwracane typy
Wzorzec TAP używa jednej metody do reprezentowania rozpoczęcia i wykonywania operacji asynchronicznej. To podejście kontrastuje zarówno ze wzorcem asynchronicznym programowania (APM lub IAsyncResult) jak i wzorcem asynchronicznym opartym na zdarzeniach (EAP). Program APM wymaga metod Begin i End. Protokół EAP wymaga metody, która ma Async sufiks, a także wymaga co najmniej jednego zdarzenia, typów delegatów programu obsługi zdarzeń i EventArgtypów pochodnych. Metody asynchroniczne w interfejsie TAP zawierają sufiks Async po nazwie operacji dla metod, które zwracają typy oczekujące, takie jak Task, Task<TResult>, ValueTask i ValueTask<TResult>. Na przykład operacja asynchroniczna Get, która zwraca Task<String>, może mieć nazwę GetAsync. Jeśli dodasz metodę TAP do klasy, która zawiera już nazwę metody protokołu EAP z sufiksem Async , użyj sufiksu TaskAsync . Jeśli na przykład klasa ma już metodę GetAsync , użyj nazwy GetTaskAsync. Jeśli metoda uruchamia operację asynchroniczną, ale nie zwraca typu oczekującego, jej nazwa powinna zaczynać się od Begin, Start lub innego czasownika, aby zasugerować, że ta metoda nie zwraca ani nie zgłasza wyniku operacji.
Metoda TAP zwraca albo System.Threading.Tasks.Task, albo System.Threading.Tasks.Task<TResult>, na podstawie tego, czy odpowiednia metoda synchroniczna zwraca void czy typ TResult.
Parametry metody TAP powinny być zgodne z parametrami jego synchronicznego odpowiednika i powinny być podane w tej samej kolejności. Jednak parametry out i ref są zwolnione z tej reguły i należy ich całkowicie unikać. Wszelkie dane, które zwracają out lub ref, powinny być częścią TResult zwracanego przez Task<TResult> i powinny używać krotki lub niestandardowej struktury danych, aby pomieścić wiele wartości. Ponadto rozważ dodanie parametru CancellationToken , nawet jeśli synchroniczny odpowiednik metody TAP nie oferuje tego parametru.
Metody przeznaczone wyłącznie do tworzenia, manipulowania lub kombinacji zadań (gdzie asynchroniczna intencja metody jest jasna w nazwie metody lub w nazwie typu, do którego należy metoda), nie muszą być zgodne z tym wzorcem nazewnictwa. Takie metody są często określane jako kombinatory. Przykłady kombinatorów to WhenAll i WhenAny, i zostały omówione w sekcji Korzystanie z wbudowanych kombinatorów opartych na zadaniach artykułu Korzystanie z asynchronicznego wzorca opartego na zadaniach.
Aby zapoznać się z przykładami różnic w składni TAP w porównaniu do składni używanej w starszych wzorcach programowania asynchronicznego, takich jak Asynchroniczny Model Programowania (APM) i Wzorzec Asynchroniczny oparty na zdarzeniach (EAP), zobacz Asynchroniczne wzorce programowania.
Zachowanie asynchroniczne, typy zwracane i nazewnictwo
Słowo kluczowe async nie wymusza, aby metoda działała asynchronicznie w innym wątku. Włącza metodę await, a metoda jest uruchamiana synchronicznie, dopóki nie osiągnie niekompletnej oczekiwanej wartości. Jeśli metoda nie napotka niekompletnego oczekiwanego obiektu, może zostać wykonana synchronicznie.
W przypadku większości interfejsów API preferuj następujące typy zwracane:
- Służy Task do wykonywania operacji asynchronicznych, które nie generują wartości.
- Służy Task<TResult> do wykonywania operacji asynchronicznych, które generują wartość.
- Użyj ValueTask lub ValueTask<TResult> tylko wtedy, gdy pomiary pokazują presję na alokację zasobów i kiedy konsumenci mogą sprostać dodatkowym ograniczeniom dotyczącym użycia.
Zachowaj przewidywalne nazewnictwo TAP.
- Użyj sufiksu
Asyncdla metod, które zwracają oczekiwane typy. - Nie dołączaj
Asyncdo metod synchronicznych. - Dodaj nowe
MethodNameAsyncprzeciążenie obok istniejącej metody "MethodName". Nie usuwaj ani nie zmieniaj nazwy synchronicznego interfejsu API. Utrzymywanie obu pozwala rozmówcom migrować we własnym tempie bez zmiany powodującej niezgodność.
Inicjowanie operacji asynchronicznej
Metoda asynchroniczna oparta na interfejsie TAP może wykonać niewielką ilość pracy synchronicznie, na przykład weryfikację argumentów i zainicjowanie operacji asynchronicznej, zanim zwróci wynikowe zadanie. Zminimalizuj pracę synchroniczną, aby metoda asynchroniczna mogła szybko się zakończyć. Przyczyny szybkiego powrotu obejmują:
- Metody asynchroniczne mogą być wywoływane z wątków interfejsu użytkownika, a każda długotrwała praca synchroniczna może zaszkodzić reakcji aplikacji.
- Możesz uruchomić wiele metod asynchronicznych jednocześnie. W związku z tym każda długotrwała praca w synchronicznej części metody asynchronicznej może opóźnić inicjowanie innych operacji asynchronicznych, zmniejszając w ten sposób korzyści wynikające ze współbieżności.
W niektórych przypadkach ilość pracy wymaganej do ukończenia operacji jest mniejsza niż ilość pracy wymaganej do asynchronicznego uruchomienia operacji. Odczytywanie ze strumienia, w którym operację odczytu można spełnić danymi już zbuforowanymi w pamięci, jest przykładem takiego scenariusza. W takich przypadkach operacja może ukończyć się synchronicznie i zwrócić zadanie, które zostało już wykonane.
Wyjątki
Metoda asynchroniczna powinna zgłaszać wyjątek bezpośrednio podczas wywołania metody asynchronicznej tylko w przypadku błędu w użyciu. Błędy użycia nigdy nie powinny występować w kodzie produkcyjnym. Jeśli na przykład przekazanie odwołania o wartości null (Nothing w Visual Basic) jako jednego z argumentów metody powoduje wystąpienie błędu (zwykle reprezentowane przez ArgumentNullException wyjątek), można zmodyfikować kod wywołujący, aby upewnić się, że odwołanie o wartości null nigdy nie zostanie przekazane. W przypadku wszystkich innych błędów przypisz wyjątki, które występują, gdy metoda asynchroniczna jest uruchomiona do zwróconego zadania, nawet jeśli metoda asynchroniczna zakończy się synchronicznie, zanim zadanie zostanie zwrócone. Zazwyczaj zadanie zawiera co najwyżej jeden wyjątek. Jeśli jednak zadanie reprezentuje wiele operacji (na przykład WhenAll), wiele wyjątków może być skojarzonych z jednym zadaniem.
Środowisko docelowe
Podczas implementowania metody TAP można określić, gdzie występuje wykonywanie asynchroniczne. Możesz wybrać uruchomienie zadania w puli wątków, zaimplementować je przy użyciu asynchronicznego we/wy (nie będąc przypisanym do jednego wątku na większość czasu trwania operacji), uruchomić je na określonym wątku (takim jak wątek interfejsu użytkownika) lub użyć różnych potencjalnych kontekstów. Metoda TAP może nawet nie mieć nic do wykonania i może po prostu zwrócić element Task reprezentujący wystąpienie warunku w innym miejscu w systemie (na przykład zadanie reprezentujące dane przychodzące do struktury danych w kolejce).
Obiekt wywołujący metodę TAP może zablokować oczekiwanie na ukończenie metody TAP przez synchroniczne oczekiwanie na wynikowe zadanie lub może uruchomić dodatkowy (kontynuacja) kod po zakończeniu operacji asynchronicznej. Twórca kodu kontynuacji ma kontrolę nad miejscem wykonywania tego kodu. Kod kontynuacji można utworzyć jawnie za pomocą metod w klasie Task (na przykład ContinueWith) lub niejawnie, używając obsługi języka opartej na kontynuacjach (na przykład await w języku C#, Await w Visual Basic, AwaitValue w języku F#).
Stan zadania
Klasa Task zapewnia cykl życia dla operacji asynchronicznych, a ten cykl jest reprezentowany przez TaskStatus wyliczenie. Aby obsługiwać skrajne przypadki typów, które pochodzą z Task i Task<TResult>, oraz aby wspierać oddzielenie konstrukcji od planowania, klasa Task udostępnia metodę Start. Zadania tworzone przez konstruktory publiczne Task są nazywane zimnymi zadaniami, ponieważ rozpoczynają swój cykl życia w stanie nieplanowanym Created i są zaplanowane tylko wtedy, gdy Start są wywoływane na tych wystąpieniach.
Wszystkie inne zadania rozpoczynają cykl życia w stanie gorąca, co oznacza, że operacje asynchroniczne, które reprezentują, są już inicjowane, a ich stan zadania to wartość wyliczenia inna niż TaskStatus.Created. Wszystkie zadania zwracane z metod TAP muszą być aktywowane. Jeśli metoda TAP wewnętrznie używa konstruktora zadania do utworzenia instancji zadania, które ma zostać zwrócone, metoda TAP musi wywołać Start na obiekcie Task przed jego zwróceniem. Użytkownicy metody TAP mogą bezpiecznie założyć, że zwrócone zadanie jest aktywne i nie powinni próbować wywołać Start na żadnym Task zwracanym z metody TAP. Wywołanie Start na aktywnym zadaniu skutkuje wyjątkiem InvalidOperationException.
Aby uzyskać wskazówki dotyczące kwestii związanych z okresem istnienia i własnością po wykonaniu zadania bez oczekiwania na zakończenie, zobacz Utrzymywanie metod asynchronicznych przy życiu.
Anulowanie (opcjonalnie)
W modelu TAP anulowanie jest opcjonalne zarówno dla implementujących asynchroniczne metody, jak i dla ich odbiorców. Jeśli operacja zezwala na anulowanie, uwidacznia przeciążenie metody asynchronicznej, która akceptuje token anulowania (CancellationToken wystąpienie). Zgodnie z konwencją parametr ma nazwę cancellationToken.
public static Task ReadAsync(byte[] buffer, int offset, int count,
CancellationToken cancellationToken)
Public Function ReadAsync(buffer As Byte(), offset As Integer, count As Integer,
cancellationToken As CancellationToken) As Task
Operacja asynchroniczna monitoruje ten token pod kątem żądań anulowania. Jeśli otrzyma żądanie anulowania, może wybrać opcję honorowania tego żądania i anulowania operacji. Jeśli żądanie anulowania spowoduje przedwczesne zakończenie pracy, metoda TAP zwróci zadanie kończące się w stanie Canceled; nie ma dostępnego wyniku i nie zostanie zgłoszony żaden wyjątek. Stan Canceled jest uznawany za stan końcowy (ukończony) dla zadania wraz z stanami Faulted i RanToCompletion . Jeśli zadanie jest w stanie Canceled, jego właściwość IsCompleted zwraca true. Po zakończeniu zadania w stanie Canceled, wszelkie kontynuacje zarejestrowane dla zadania są planowane lub wykonywane, chyba że określono opcję kontynuacji, taką jak NotOnCanceled, aby zrezygnować z kontynuacji. Każdy kod, który oczekuje na anulowane zadanie asynchronicznie za pomocą funkcji językowych, nadal działa, ale otrzymuje OperationCanceledException lub wyjątek pochodny. Kod, który jest blokowany synchronicznie czekając na zadanie za pomocą metod, takich jak Wait i WaitAll nadal działa z wyjątkiem.
Jeśli token anulowania żąda anulowania przed wywołaniem metody TAP, która akceptuje ten token, metoda TAP powinna zwrócić Canceled zadanie. Jeśli jednak zażądano anulowania podczas wykonywania operacji asynchronicznej, operacja asynchroniczna nie musi zaakceptować żądania anulowania. Zadanie, które zostało zwrócone, powinno zakończyć się w Canceled stanie tylko wtedy, gdy operacja kończy się wskutek żądania anulowania. Jeśli anulowanie jest żądane, ale wynik lub wyjątek nadal powstaje, zadanie powinno zakończyć się w stanie RanToCompletion lub Faulted.
W przypadku metod asynchronicznych, które chcą uwidocznić możliwość anulowania przede wszystkim, nie trzeba podawać przeciążenia, które nie akceptuje tokenu anulowania. W przypadku metod, których nie można anulować, nie udostępniaj przeciążeń akceptujących token anulowania; pomaga to wywołującemu ustalić, czy metoda docelowa jest rzeczywiście anulowalna. Kod użytkownika nie wymagający anulowania może wywołać metodę, która akceptuje CancellationToken i podaje None jako wartość argumentu. None jest funkcjonalnie odpowiednikiem domyślnego CancellationTokenelementu .
Raportowanie postępu (opcjonalnie)
Niektóre operacje asynchroniczne korzystają z dostarczania powiadomień o postępie. Zazwyczaj te powiadomienia służą do aktualizowania interfejsu użytkownika przy użyciu informacji o postępie operacji asynchronicznej.
W TAP obsługuj postęp przez interfejs IProgress<T>. Przekaż ten interfejs do metody asynchronicznej jako parametru, zwykle o nazwie progress. Gdy podajesz interfejs śledzenia postępu w momencie wywołania metody asynchronicznej, możesz wyeliminować warunki wyścigu wynikające z niewłaściwego użycia. te warunki wyścigu występują, gdy programy obsługi zdarzeń są niepoprawnie zarejestrowane po rozpoczęciu operacji i omijają aktualizacje. Co ważniejsze, interfejs aktualizacji obsługuje różne implementacje aktualizacji, określone przez kod korzystający. Na przykład kod korzystający może dbać tylko o najnowszą aktualizację postępu lub może chcieć buforować wszystkie aktualizacje, wywoływać akcję dla każdej aktualizacji lub kontrolować, czy wywołanie jest przekierowane do określonego wątku. Wszystkie te opcje są osiągalne przy użyciu różnych implementacji interfejsu dostosowanego do potrzeb określonego konsumenta. Podobnie jak w przypadku anulowania implementacje interfejsu TAP powinny podać IProgress<T> parametr tylko wtedy, gdy interfejs API obsługuje powiadomienia o postępie.
Na przykład, jeśli metoda ReadAsync, omawiana wcześniej w tym artykule, może zgłaszać bieżący postęp poprzez liczbę odczytanych do tej pory bajtów, wywołanie zwrotne postępu może być interfejsem IProgress<T>.
public static Task ReadAsync(byte[] buffer, int offset, int count,
IProgress<long> progress)
Public Function ReadAsync(buffer As Byte(), offset As Integer, count As Integer,
progress As IProgress(Of Long)) As Task
FindFilesAsync Jeśli metoda zwraca listę wszystkich plików spełniających określony wzorzec wyszukiwania, wywołanie zwrotne postępu może zapewnić oszacowanie procentu wykonanej pracy i bieżącego zestawu wyników częściowych. Te informacje mogą zawierać krotkę:
public static Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<Tuple<double, ReadOnlyCollection<List<FileInfo>>>> progress)
Public Function FindFilesAsync(
pattern As String,
progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) As Task(Of ReadOnlyCollection(Of FileInfo))
lub z typem danych specyficznym dla interfejsu API:
public static Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<FindFilesProgressInfo> progress)
Public Function FindFilesAsync(
pattern As String,
progress As IProgress(Of FindFilesProgressInfo)) As Task(Of ReadOnlyCollection(Of FileInfo))
W tym drugim przypadku do specjalnego typu danych zazwyczaj dodawany jest sufiks ProgressInfo.
Jeśli implementacje TAP zapewniają przeciążenia, które akceptują progress parametr, muszą umożliwiać, aby argument mógł być null. Jeśli przejdziesz null, nie zostanie zgłoszony żaden postęp. Implementacje TAP powinny zgłaszać postęp do Progress<T> obiektu synchronicznie, co umożliwia metodzie asynchronicznej szybkie zapewnienie postępu. Umożliwia również konsumentowi postępów określenie, jak i gdzie najlepiej obsługiwać informacje. Na przykład instancja postępu może zdecydować się na zarządzanie wywołaniami zwrotnymi oraz uruchomienie zdarzeń w przechwyconym kontekście synchronizacji.
Implementacje IProgress<T>
Platforma .NET udostępnia klasę Progress<T>, która implementuje IProgress<T>. Klasa Progress<T> jest zadeklarowana w następujący sposób:
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T>? ProgressChanged;
}
Wystąpienie Progress<T> uwidacznia ProgressChanged zdarzenie, które jest wywoływane za każdym razem, gdy operacja asynchroniczna zgłasza aktualizację postępu. Zdarzenie ProgressChanged jest wywoływane na obiekcie SynchronizationContext, który jest przechwytywany podczas instancjacji przez instancję Progress<T>. Jeśli nie jest dostępny kontekst synchronizacji, jest używany domyślny kontekst przeznaczony dla puli wątków. Możesz zarejestrować programy obsługi w tym zdarzeniu. Dla wygody można również udostępnić jedną procedurę obsługi konstruktorowi Progress<T> . Ta procedura obsługi działa tak samo jak procedura obsługi zdarzeń dla ProgressChanged zdarzenia. Aktualizacje postępu są wywoływane asynchronicznie, aby uniknąć opóźnienia operacji asynchronicznej podczas wykonywania procedur obsługi zdarzeń. Inna IProgress<T> implementacja może zdecydować się na zastosowanie różnych semantyki.
Wybieranie przeciążeń, które mają być podane
Jeśli implementacja interfejsu TAP używa zarówno opcjonalnych CancellationToken , jak i opcjonalnych IProgress<T> parametrów, potencjalnie może wymagać maksymalnie czterech przeciążeń:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
public Task MethodNameAsync(…, IProgress<T> progress);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken cancellationToken) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
Jednak wiele implementacji TAP nie zapewnia możliwości anulowania ani śledzenia postępu, dlatego wymagają użycia jednej metody.
public Task MethodNameAsync(…);
Public MethodNameAsync(…) As Task
Jeśli implementacja interfejsu TAP obsługuje anulowanie lub postęp, ale nie oba, może zapewnić dwa przeciążenia:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
// … or …
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken) As Task
' … or …
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Jeśli implementacja interfejsu TAP obsługuje zarówno anulowanie, jak i postęp, może ujawnić wszystkie cztery przeciążenia. Może jednak zawierać tylko następujące dwa:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
Aby zrekompensować dwie brakujące kombinacje pośrednie, deweloperzy mogą przekazać None lub wartość domyślną CancellationToken dla parametru cancellationToken oraz null dla parametru progress.
Jeśli oczekujesz, że każde użycie metody TAP będzie obsługiwać anulowanie lub raportowanie postępu, możesz zrezygnować z przeciążeń, które nie akceptują odpowiedniego parametru.
Jeśli zdecydujesz się udostępnić wiele przeciążeń, aby umożliwić opcjonalne anulowanie lub śledzenie postępu, przeciążenia, które nie obsługują anulowania lub postępu, powinny działać tak, jakby przekazywały parametr None dla anulowania lub null dla postępu do przeciążenia, które obsługuje te parametry.
Powiązane artykuły
- Wzorce programowania asynchronicznego — wprowadza trzy wzorce do wykonywania operacji asynchronicznych: asynchroniczny wzorzec oparty na zadaniach (TAP), asynchroniczny model programowania (APM) i wzorzec asynchroniczny oparty na zdarzeniach (EAP).
- Implementowanie wzorca asynchronicznego opartego na zadaniach — opisuje sposób implementowania interfejsu TAP na trzy sposoby: przy użyciu języka C# i Visual Basic kompilatorów w Visual Studio, ręcznie lub za pomocą kombinacji metod kompilatora i metod ręcznych.
- Korzystanie ze wzorca asynchronicznego opartego na zadaniach — opisuje sposób użycia zadań i wywołań zwrotnych w celu osiągnięcia oczekiwania bez blokowania.
- Współpraca z innymi wzorcami i typami asynchronicznymi — przedstawia sposób użycia TAP do implementacji Asynchronous Programming Model (APM) i Event-based Asynchronous Pattern (EAP).