Asynchronní vzor založený na úlohách (TAP) v .NET: Úvod a přehled

V .NET je vzor asynchronního návrhu založený na úlohách doporučeným vzorem asynchronního návrhu pro nový vývoj. Je založená na typech TaskTask<TResult> v System.Threading.Tasks oboru názvů, které se používají k reprezentaci asynchronních operací.

Pojmenování, parametry a návratové typy

TAP používá jedinou metodu k reprezentaci zahájení a dokončení asynchronní operace. To kontrastuje se vzorem asynchronního programovacího modelu (APM) a IAsyncResultasynchronním vzorem založeným na událostech (EAP). APM vyžaduje Begin a End metody. Protokol EAP vyžaduje metodu s příponou Async a také vyžaduje jednu nebo více událostí, typy delegátů obslužné rutiny událostí a EventArgodvozené typy. Asynchronní metody v TAP zahrnují příponu Async za názvem operace pro metody, které vracejí očekávané typy, například Task, Task<TResult>, ValueTaska ValueTask<TResult>. Například asynchronní Get operace, která vrací Task<String> hodnotu lze pojmenovat GetAsync. Pokud přidáváte metodu TAP do třídy, která už obsahuje název metody EAP s Async příponou, použijte místo toho příponu TaskAsync . Například pokud třída již má metodu GetAsync , použijte název GetTaskAsync. Pokud metoda spustí asynchronní operaci, ale nevrací očekávaný typ, jeho název by měl začínat znakem Begin, Startnebo jiným slovesem navrhnout, že tato metoda nevrací nebo vyvolá výsledek operace.  

Metoda TAP vrátí buď a System.Threading.Tasks.Task nebo , System.Threading.Tasks.Task<TResult>na základě toho, zda odpovídající synchronní metoda vrací void nebo typ TResult.

Parametry metody TAP by měly odpovídat parametrům synchronního protějšku a měly by být poskytnuty ve stejném pořadí. Parametry ref jsou out však z tohoto pravidla vyloučené a měly by se jim zcela vyhnout. Všechna data, která by byla vrácena prostřednictvím parametru out nebo ref která by byla vrácena, by měla být vrácena jako součást TResult vrácených Task<TResult>funkcí a měla by použít řazenou kolekci členů nebo vlastní datovou strukturu, aby vyhovovala více hodnotám. Zvažte také přidání parametru CancellationToken i v případě, že synchronní protějšek metody TAP ho nenabízí.

Metody, které jsou vyhrazeny výhradně pro vytváření, manipulaci nebo kombinaci úloh (kde asynchronní záměr metody je jasný v názvu metody nebo v názvu typu, do kterého metoda patří), nemusí postupovat podle tohoto vzoru pojmenování; tyto metody se často označují jako kombinátory. Příklady kombinátorů zahrnují WhenAll a WhenAnya jsou popsány v části Použití předdefinovaných kombinátorů založených na úlohách článku Využívání asynchronního vzoru založeného na úlohách.

Příklady, jak se syntaxe TAP liší od syntaxe používané ve starších vzorech asynchronního programování, jako je asynchronní programovací model (APM) a asynchronní vzor založený na událostech (EAP), najdete v tématu Asynchronní programovací vzory.

Iniciování asynchronní operace

Asynchronní metoda, která je založena na TAP, zvládne malé množství práce synchronně, například validaci argumentů a spouštění asynchronní operace před vrácením výsledné úlohy. Synchronní práce by měly být neustále udržovány na minimu, aby asynchronní metody mohly vracet rychle. Mezi důvody rychlého vrácení patří:

  • Asynchronní metody mohou být vyvolány z vlákna uživatelského rozhraní (UI) a dlouhotrvající synchronní práce může mít negativní dopad na odezvu aplikace.

  • Je možné spustit několik asynchronních metod současně. Proto by jakékoli dlouhotrvající práce v synchronní části asynchronní metody mohly zpožďovat zahájení jiné asynchronní operace, a tím zmenšit výhody souběžnosti.

V některých případech je množství práce potřebné k dokončení operace menší než množství práce potřebné ke spuštění operace asynchronně. Příkladem takového scénáře je čtení z datového proudu, kde lze operaci čtení naplnit daty, která jsou již uložena do vyrovnávací paměti. Operace v těchto případech může být dokončena synchronně a může vrátit úlohu, která již byla dokončena.

Výjimky

Asynchronní metoda by měla vyvolat výjimku z volání asynchronní metody pouze jako odpověď na chybu použití. Chyby použití by se nikdy neměly objevit v produkčním kódu. Pokud například předáte odkaz null (Nothing v jazyce Visual Basic) jako jeden z argumentů metody způsobí chybový stav (obvykle reprezentovaný ArgumentNullException výjimkou), můžete upravit volající kód tak, aby se zajistilo, že odkaz null nikdy nepřejde. U všech ostatních chyb by měly být výjimky, ke kterým dochází při spuštění asynchronní metody, přiřazeny vrácené úloze i v případě, že se asynchronní metoda dokončí synchronně předtím, než je úloha vrácena. Úloha obvykle obsahuje nanejvýš jednu výjimku. Pokud však úkol představuje více operací (například WhenAll), může být k jednomu úkolu přidruženo více výjimek.

Cílové prostředí

Při implementaci metody TAP můžete určit, kde dochází k asynchronnímu spouštění. Můžete se rozhodnout spustit úlohu ve fondu vláken, implementovat ji pomocí asynchronních vstupně-výstupních operací (bez vazby na vlákno pro většinu provádění operace), spustit ji na konkrétním vlákně (například vlákně uživatelského rozhraní) nebo použít libovolný počet potenciálních kontextů. Metoda TAP může mít dokonce nic ke spuštění a může jen vrátit Task , která představuje výskyt podmínky jinde v systému (například úloha představující data přicházející do fronty datové struktury).

Volající metody TAP může blokovat čekání na dokončení metody TAP synchronním čekáním na výslednou úlohu nebo může po dokončení asynchronní operace spustit další kód (pokračování). Tvůrce kódu pokračování má kontrolu nad tím, kde se spustí kód. Kód pokračování můžete vytvořit explicitně prostřednictvím metod třídy Task (například ContinueWith) nebo implicitně pomocí podpory jazyka založené na pokračováních (například await v jazyce C#, Await v jazyce Visual Basic AwaitValue v jazyce F#).

Stav úkolu

Třída Task poskytuje životní cyklus pro asynchronní operace a tento cyklus je reprezentován výčtem TaskStatus . Chcete-li podporovat rohové případy typů odvozených od Task a Task<TResult>, a podporovat oddělení konstrukce od plánování, Task třída zveřejňuje metodu Start . Úlohy vytvořené veřejnými Task konstruktory jsou označovány jako studené úkoly, protože jejich životní cyklus začíná v neplánované Created stavu a jsou naplánovány pouze v případě, že Start je volána na těchto instancích.

Všechny ostatní úkoly začínají svůj životní cyklus v horkém stavu, což znamená, že asynchronní operace, které představují, již byly zahájeny a jejich stav úkolu je hodnota výčtu jiná než TaskStatus.Created. Všechny úlohy, které jsou vráceny z metod TAP, musí být aktivovány. Pokud metoda TAP interně používá konstruktor úkolu k vytvoření instance úkolu, který má být vrácen, musí metoda TAP volat Start objekt Task před vrácením. Uživatelé metody TAP mohou bezpečně předpokládat, že vrácený úkol je aktivní a neměl by se pokoušet volat Start žádné Task vrácené metodou TAP. Volání Start aktivního úkolu způsobí InvalidOperationException výjimku.

Zrušení (volitelné)

Pro implementátory asynchronní metody i příjemce asynchronní metody je v TAP zrušení volitelné. Pokud operace umožňuje zrušení, zveřejňuje přetížení asynchronní metody, která přijímá token zrušení (CancellationToken instance). Podle konvence má parametr název cancellationToken.

public 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

Asynchronní operace sleduje tento token kvůli žádostem o zrušení. Pokud obdrží žádost o zrušení, může vyhovět žádosti a operaci zrušit. Pokud se žádost o zrušení ukončí předčasně, vrátí metoda TAP úlohu, která končí ve Canceled stavu; neexistuje žádný dostupný výsledek a nevyvolá se žádná výjimka. Stav Canceled se považuje za konečný (dokončený) stav úkolu spolu se Faulted stavy a RanToCompletion stavy. Proto pokud je úkol ve Canceled stavu, jeho IsCompleted vlastnost vrátí true. Po dokončení úkolu ve Canceled stavu jsou všechna pokračování zaregistrovaná u úkolu naplánovaná nebo spuštěná, pokud není zadána možnost pokračování, jako NotOnCanceled je například možnost vyřazení z pokračování. Veškerý kód, který asynchronně čeká na zrušenou úlohu pomocí funkcí jazyka, se bude dál spouštět, ale obdrží OperationCanceledException výjimku nebo výjimku odvozenou z ní. Kód, který je blokovaný synchronně čekající na úlohu prostřednictvím metod, jako Wait je a WaitAll také pokračovat ve spuštění s výjimkou.

Pokud token zrušení požádal o zrušení před metodou TAP, která přijímá tento token je volána, metoda TAP by měla vrátit Canceled úlohu. Pokud je však požadováno zrušení při spuštěné asynchronní operaci, asynchronní operace nemusí přijmout žádost o zrušení. Vrácený úkol by měl končit ve Canceled stavu pouze v případě, že operace skončí v důsledku požadavku na zrušení. Pokud je požadováno zrušení, ale výsledek nebo výjimka je stále vytvořena, úkol by měl končit ve RanToCompletion stavu nebo Faulted ve stavu.

Pro asynchronní metody, které chtějí zpřístupnit možnost zrušení především, nemusíte poskytovat přetížení, které nepřijímá token zrušení. Pro metody, které nelze zrušit, neposkytujte přetížení, která přijímají token zrušení. To pomáhá volajícímu určit, zda je skutečně možné zrušit cílovou metodu. Spotřebitelský kód, který nechce zrušení, může volat metodu, která přijímá CancellationToken a poskytuje None jako hodnotu argumentu. None je funkčně ekvivalentní výchozímu CancellationToken.

Vykazování průběhu (volitelné)

Některé asynchronní operace těží z poskytování oznámení o průběhu. Ta se obvykle používají k aktualizaci uživatelského rozhraní informacemi o průběhu asynchronní operace.

V TAP se průběh zpracovává prostřednictvím IProgress<T> rozhraní, které se předává asynchronní metodě jako parametr, který je obvykle pojmenován progress. Poskytnutí rozhraní průběhu při volání asynchronní metody pomáhá eliminovat konflikty časování, které jsou výsledkem nesprávného použití (to znamená, když obslužným rutinám, které jsou nesprávně zaregistrovány po zahájení operace, chybí aktualizace). Důležitější je, že rozhraní průběhu podporuje různé implementace průběhu podle náročnosti kódu. Využívání kódu se například může starat pouze o nejnovější aktualizaci průběhu nebo může chtít ukládat všechny aktualizace do vyrovnávací paměti, nebo může chtít vyvolat akci pro každou aktualizaci nebo může chtít řídit, zda je vyvolání zařazováno do určitého vlákna. Všechny tyto možnosti lze dosáhnout pomocí jiné implementace rozhraní, které je přizpůsobené potřebám konkrétního příjemce. Stejně jako u zrušení by implementace TAP měly poskytovat IProgress<T> parametr pouze v případě, že rozhraní API podporuje oznámení o průběhu.

Pokud ReadAsync například metoda probíraná dříve v tomto článku dokáže hlásit průběžný průběh ve formě dosud přečteného počtu bajtů, může IProgress<T> být zpětné volání průběhu rozhraním:

public 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 Pokud metoda vrátí seznam všech souborů, které splňují určitý vzor hledání, zpětné volání průběhu může poskytnout odhad procenta dokončené práce a aktuální sadu částečných výsledků. Tyto informace by mohly poskytnout buď řazenou kolekci členů:

public 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))

nebo s datovým typem, který je specifický pro rozhraní API:

public 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))

V druhém případě je speciální datový typ obvykle příponou ProgressInfo.

Pokud implementace TAP poskytují přetížení, které přijímají progress parametr, musí povolit argument být null, v takovém případě není hlášen žádný pokrok. Implementace TAP by měly hlásit průběh Progress<T> objektu synchronně, což umožňuje asynchronní metodě rychle poskytnout průběh. Umožňuje také příjemci průběhu určit, jak a kde nejlépe zpracovávat informace. Například instance průběhu může zvolit zařazování zpětných volání a vyvolat události na zachyceném synchronizačním kontextu.

Implementace IProgress<T>

.NET poskytuje Progress<T> třídu, která implementuje IProgress<T>. Třída Progress<T> je deklarována takto:

public class Progress<T> : IProgress<T>  
{  
    public Progress();  
    public Progress(Action<T> handler);  
    protected virtual void OnReport(T value);  
    public event EventHandler<T>? ProgressChanged;  
}  

Instance Progress<T> zveřejňuje ProgressChanged událost, která se vyvolá pokaždé, když asynchronní operace hlásí aktualizaci průběhu. Událost ProgressChanged je vyvolána u objektu SynchronizationContext , který byl zachycen při Progress<T> vytvoření instance instance. Pokud nebyl k dispozici žádný kontext synchronizace, je použit výchozí kontext určený pro fond vláken. Pro tuto událost lze registrovat obslužné rutiny. Jeden obslužný rutina může být také k dispozici Progress<T> konstruktoru pro usnadnění a chová se stejně jako obslužná rutina události události ProgressChanged . Aktualizace průběhu jsou vyvolány asynchronně, aby se předešlo zpoždění asynchronní operace, zatímco se provádějí obslužné rutiny události. Jiná IProgress<T> implementace by se mohla rozhodnout použít jinou sémantiku.

Volba přetížení, která se mají poskytnout

Pokud implementace TAP používá volitelné CancellationToken i volitelné IProgress<T> parametry, může to potenciálně vyžadovat až čtyři přetížení:

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  

Mnoho implementací TAP ale neposkytuje možnosti zrušení ani průběhu, takže vyžadují jednu metodu:

public Task MethodNameAsync(…);  
Public MethodNameAsync(…) As Task  

Pokud implementace TAP podporuje buď zrušení, nebo průběh, ale ne oboje, může poskytnout dvě přetížení:

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  

Pokud implementace TAP podporuje zrušení i průběh, může poskytnout všechna čtyři přetížení. Může však také poskytnout pouze dvě následující:

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 vývojáři mohli tyto dvě chybějící přechodné kombinace kompenzovat, mohou předat parametru a null parametru progress výchozí nastavení CancellationTokencancellationToken.None

Pokud očekáváte, že každé použití metody TAP podporuje zrušení nebo průběh, můžete vynechat přetížení, která nepřijímají příslušný parametr.

Pokud se rozhodnete zveřejnit více přetížení, aby bylo zrušení nebo průběh volitelné, přetížení, která nepodporují zrušení nebo průběh, by se měla chovat, jako by prošla None pro zrušení nebo null průběh přetížení, které je podporuje.

Titulek Popis
Vzory asynchronního programování Zavádí tři vzory pro provádění asynchronních operací: synchronní vzor založený na úlohách (TAP), asynchronní programovací model (APM) a asynchronní vzor založený na událostech (EAP).
Implementace asynchronního vzoru založeného na úlohách Popisuje tři způsoby implementace asynchronního vzoru založeného na úlohách (TAP): pomocí kompilátorů jazyka C# a Visual Basic v sadě Visual Studio, ručně nebo kombinací obou metod.
Použití asynchronního vzoru založeného na úlohách Popisuje, jak použít úlohy a zpětná volání k dosažení čekání bez blokování.
Interoperabilita s jinými asynchronními vzory a typy Popisuje způsob použití asynchronního vzoru založeného na úlohách (TAP) k implementaci asynchronního programovacího modelu (APM) a asynchronního vzoru založeného na událostech (EAP).