Implementace asynchronního vzoru založeného na úloháchImplementing the Task-based Asynchronous Pattern

Asynchronní vzor založený na úkolech (TAP) můžete implementovat třemi způsoby: pomocí kompilátorů jazyka C# a Visual Basic v sadě Visual Studio, ručně nebo kombinací obou metod.You can implement the Task-based Asynchronous Pattern (TAP) in three ways: by using the C# and Visual Basic compilers in Visual Studio, manually, or through a combination of the compiler and manual methods. Jednotlivé metody jsou podrobně popsány v následujících částech.The following sections discuss each method in detail. Můžete použít vzor klepnutí pro implementaci asynchronních operací vázaných na vstupně-výstupní operace I/O.You can use the TAP pattern to implement both compute-bound and I/O-bound asynchronous operations. Část úlohy popisuje jednotlivé typy operací.The Workloads section discusses each type of operation.

Generování klepnutím na metodyGenerating TAP methods

Použití kompilátorůUsing the compilers

Počínaje .NET Framework 4,5 je každá metoda, která je označena klíčovým slovem async (Async v Visual Basic) považována za asynchronní metodu a kompilátory C# a Visual Basic provádí potřebné transformace k implementaci Metoda asynchronně pomocí klepnutí.Starting with .NET Framework 4.5, any method that is attributed with the async keyword (Async in Visual Basic) is considered an asynchronous method, and the C# and Visual Basic compilers perform the necessary transformations to implement the method asynchronously by using TAP. Asynchronní metoda by měla vrátit buď objekt System.Threading.Tasks.Task, nebo objekt System.Threading.Tasks.Task<TResult>.An asynchronous method should return either a System.Threading.Tasks.Task or a System.Threading.Tasks.Task<TResult> object. V takovém případě by tělo funkce mělo vracet TResulta kompilátor zajistí, že se tento výsledek zpřístupní prostřednictvím výsledného objektu Task.For the latter, the body of the function should return a TResult, and the compiler ensures that this result is made available through the resulting task object. Stejně tak veškeré výjimky, které nejsou zpracovány v těle metody, jsou zařazeny do výstupního úkolu, což způsobí, že výsledný úkol bude ukončen ve stavu TaskStatus.Faulted.Similarly, any exceptions that go unhandled within the body of the method are marshaled to the output task and cause the resulting task to end in the TaskStatus.Faulted state. Výjimkou je situace, kdy není zpracována výjimka typu OperationCanceledException (nebo odvozeného typu). V takovém případě skončí výsledný úkol ve stavu TaskStatus.Canceled.The exception is when an OperationCanceledException (or derived type) goes unhandled, in which case the resulting task ends in the TaskStatus.Canceled state.

Ruční generování klepnutí na metodyGenerating TAP methods manually

Vzor TAP můžete implementovat ručně a dosáhnout tak lepší kontroly nad implementací.You may implement the TAP pattern manually for better control over implementation. Kompilátor spoléhá na veřejnou oblast vystavenou z oboru názvů System.Threading.Tasks s podporou typů v oboru názvů System.Runtime.CompilerServices.The compiler relies on the public surface area exposed from the System.Threading.Tasks namespace and supporting types in the System.Runtime.CompilerServices namespace. Při vlastní implementaci vzoru TAP vytvoříte objekt TaskCompletionSource<TResult>, provedete asynchronní operaci a po jejím dokončení zavoláte metodu SetResult, SetException nebo SetCanceled anebo verzi Try jedné z těchto metod.To implement the TAP yourself, you create a TaskCompletionSource<TResult> object, perform the asynchronous operation, and when it completes, call the SetResult, SetException, or SetCanceled method, or the Try version of one of these methods. Při ruční implementaci metody TAP musíte dokončit výsledný úkol po dokončení zastoupené asynchronní operace.When you implement a TAP method manually, you must complete the resulting task when the represented asynchronous operation completes. Příklad:For example:

public static Task<int> ReadTask(this Stream stream, byte[] buffer, int offset, int count, object state)
{
    var tcs = new TaskCompletionSource<int>();
    stream.BeginRead(buffer, offset, count, ar =>
    {
        try { tcs.SetResult(stream.EndRead(ar)); }
        catch (Exception exc) { tcs.SetException(exc); }
    }, state);
    return tcs.Task;
}
<Extension()>
Public Function ReadTask(stream As Stream, buffer() As Byte, 
                         offset As Integer, count As Integer, 
                         state As Object) As Task(Of Integer)
    Dim tcs As New TaskCompletionSource(Of Integer)()
    stream.BeginRead(buffer, offset, count, Sub(ar)
               Try  
                  tcs.SetResult(stream.EndRead(ar)) 
               Catch exc As Exception 
                  tcs.SetException(exc)
               End Try
            End Sub, state)
    Return tcs.Task
End Function   

Hybridní přístupHybrid approach

Pravděpodobně pro vás bude užitečné, pokud implementujete vzor TAP ručně, ale delegujete základní logiku pro implementaci na kompilátor.You may find it useful to implement the TAP pattern manually but to delegate the core logic for the implementation to the compiler. Hybridní přístup budete pravděpodobně uplatňovat při ověřování argumentů mimo kompilátorem generovanou asynchronní metodu tak, aby výjimky mohly namísto vystavení prostřednictvím objektu System.Threading.Tasks.Task uniknout přímému volajícímu metody:For example, you may want to use the hybrid approach when you want to verify arguments outside a compiler-generated asynchronous method so that exceptions can escape to the method’s direct caller rather than being exposed through the System.Threading.Tasks.Task object:

public Task<int> MethodAsync(string input)
{
    if (input == null) throw new ArgumentNullException("input");
    return MethodAsyncInternal(input);
}

private async Task<int> MethodAsyncInternal(string input)
{

   // code that uses await goes here

   return value;
}
Public Function MethodAsync(input As String) As Task(Of Integer)
    If input Is Nothing Then Throw New ArgumentNullException("input")

    Return MethodAsyncInternal(input)
End Function

Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)

   ' code that uses await goes here
   
   return value
End Function

Dalším užitečným použitím takového delegování je implementace optimalizace rychlé cesty a vrácení úkolu v mezipaměti.Another case where such delegation is useful is when you're implementing fast-path optimization and want to return a cached task.

ÚkolyWorkloads

Jako metody TAP lze implementovat výpočetní i vstupně-výstupní asynchronní operace.You may implement both compute-bound and I/O-bound asynchronous operations as TAP methods. Jsou-li však metody TAP vystaveny veřejně z knihovny, měly by být poskytnuty pouze pro úkoly, které se týkají vstupně-výstupních operací (mohou také zahrnovat výpočet, ale neměly by být čistě výpočetní).However, when TAP methods are exposed publicly from a library, they should be provided only for workloads that involve I/O-bound operations (they may also involve computation, but should not be purely computational). Je-li metoda čistě výpočetní – vázána, měla by být vystavena pouze jako synchronní implementace.If a method is purely compute-bound, it should be exposed only as a synchronous implementation. Kód, který ji využívá, pak může zvolit, zda zabalit vyvolání této synchronní metody do úlohy pro přesměrování práce do jiného vlákna nebo pro dosažení paralelismu.The code that consumes it may then choose whether to wrap an invocation of that synchronous method into a task to offload the work to another thread or to achieve parallelism. A pokud je metoda vázána na vstupně-výstupní operace, měla by být vystavena pouze jako asynchronní implementace.And if a method is I/O-bound, it should be exposed only as an asynchronous implementation.

Výpočetní úkoly – vázané úlohyCompute-bound tasks

Třída System.Threading.Tasks.Task je nejvhodnější pro zastoupení výpočetně náročných operací.The System.Threading.Tasks.Task class is ideally suited for representing computationally intensive operations. Ve výchozím nastavení využívá speciální podporu v rámci třídy ThreadPool za účelem poskytování účinného provádění a zároveň poskytuje významnou kontrolu nad tím, kdy, kde a jakým způsobem lze provádět asynchronní výpočty.By default, it takes advantage of special support within the ThreadPool class to provide efficient execution, and it also provides significant control over when, where, and how asynchronous computations execute.

Úlohy vázané na výpočetní výkon můžete vygenerovat následujícími způsoby:You can generate compute-bound tasks in the following ways:

  • V rozhraní .NET Framework 4 použijte metodu TaskFactory.StartNew, jež přijímá delegát (obvykle typu Action<T> nebo Func<TResult>), který se provádí asynchronně.In the .NET Framework 4, use the TaskFactory.StartNew method, which accepts a delegate (typically an Action<T> or a Func<TResult>) to be executed asynchronously. Pokud poskytnete delegát typu Action<T>, vrátí metoda objekt System.Threading.Tasks.Task, který představuje asynchronní provádění tohoto delegátu.If you provide an Action<T> delegate, the method returns a System.Threading.Tasks.Task object that represents the asynchronous execution of that delegate. Pokud zadáte delegát typu Func<TResult>, vrátí metoda objekt System.Threading.Tasks.Task<TResult>.If you provide a Func<TResult> delegate, the method returns a System.Threading.Tasks.Task<TResult> object. Přetížení metody StartNew přijímá token zrušení (CancellationToken), možnosti vytvoření úkolu (TaskCreationOptions) a plánovač úkolů (TaskScheduler), které poskytují detailní kontrolu nad plánováním a prováděním úkolu.Overloads of the StartNew method accept a cancellation token (CancellationToken), task creation options (TaskCreationOptions), and a task scheduler (TaskScheduler), all of which provide fine-grained control over the scheduling and execution of the task. Instance objektu pro vytváření úkolů, která je určena pro aktuální plánovač úkolů, je k dispozici jako statická vlastnost (Factory) třídy Task, například Task.Factory.StartNew(…).A factory instance that targets the current task scheduler is available as a static property (Factory) of the Task class; for example: Task.Factory.StartNew(…).

  • V .NET Framework 4,5 a novějších verzích (včetně .NET Core a .NET Standard) použijte jako zástupce TaskFactory.StartNewmetodu static Task.Run.In the .NET Framework 4.5 and later versions (including .NET Core and .NET Standard), use the static Task.Run method as a shortcut to TaskFactory.StartNew. Metodu Run můžete použít pro snadné spouštění výpočetního úkolu, který se zaměřuje na fond vláken.You may use Run to easily launch a compute-bound task that targets the thread pool. V .NET Framework 4,5 a novějších verzích je to preferovaný mechanismus pro spouštění výpočetní úlohy.In the .NET Framework 4.5 and later versions, this is the preferred mechanism for launching a compute-bound task. StartNew lze použít přímo pouze v případě, že chcete mít podrobnější kontrolu nad úkolem.Use StartNew directly only when you want more fine-grained control over the task.

  • Pokud chcete generovat a plánovat úkol samostatně, použijte konstruktory typu Task nebo metodu Start.Use the constructors of the Task type or the Start method if you want to generate and schedule the task separately. Veřejné metody smí vracet pouze úkoly, které již byly zahájeny.Public methods must only return tasks that have already been started.

  • Použijte přetížení metody Task.ContinueWith.Use the overloads of the Task.ContinueWith method. Tato metoda vytvoří nový úkol, jehož spuštění je naplánováno po dokončení jiného úkolu.This method creates a new task that is scheduled when another task completes. Některá přetížení ContinueWith přijímají token zrušení, možnosti pokračování a plánovač úkolů pro lepší kontrolu nad plánováním a prováděním úkolu pokračování.Some of the ContinueWith overloads accept a cancellation token, continuation options, and a task scheduler for better control over the scheduling and execution of the continuation task.

  • Použijte metody TaskFactory.ContinueWhenAll a TaskFactory.ContinueWhenAny.Use the TaskFactory.ContinueWhenAll and TaskFactory.ContinueWhenAny methods. Tyto metody vytvoří nový úkol, který je naplánován na dobu, kdy skončí všechny nebo libovolná zadaná sada úkolů.These methods create a new task that is scheduled when all or any of a supplied set of tasks completes. Tyto metody také poskytují přetížení pro řízení plánování a provádění těchto úkolů.These methods also provide overloads to control the scheduling and execution of these tasks.

V případě výpočetních úkolů může systém zabránit spuštění naplánovaného úkolu, pokud obdrží požadavek na zrušení před spuštěním úkolu.In compute-bound tasks, the system can prevent the execution of a scheduled task if it receives a cancellation request before it starts running the task. Pokud je takto poskytován token zrušení (CancellationToken), můžete tento token předat asynchronnímu kódu, který tento token sleduje.As such, if you provide a cancellation token (CancellationToken object), you can pass that token to the asynchronous code that monitors the token. Token můžete také poskytnout jedné z výše uvedených metod, jako je StartNew nebo Run, aby modul runtime typu Task mohl tento token také sledovat.You can also provide the token to one of the previously mentioned methods such as StartNew or Run so that the Task runtime may also monitor the token.

Zvažte například asynchronní metodu, která vytváří obrázek.For example, consider an asynchronous method that renders an image. Tělo úkolu může dotazovat token zrušení tak, aby se kód mohl předčasně ukončit, pokud bude požadavek na zrušení doručen během vykreslování.The body of the task can poll the cancellation token so that the code may exit early if a cancellation request arrives during rendering. Pokud bude navíc požadavek na zrušení doručen před spuštěním vykreslování, budete chtít zabránit operaci vykreslování:In addition, if the cancellation request arrives before rendering starts, you'll want to prevent the rendering operation:

internal Task<Bitmap> RenderAsync(
              ImageData data, CancellationToken cancellationToken)
{
    return Task.Run(() =>
    {
        var bmp = new Bitmap(data.Width, data.Height);
        for(int y=0; y<data.Height; y++)
        {
            cancellationToken.ThrowIfCancellationRequested();
            for(int x=0; x<data.Width; x++)
            {
                // render pixel [x,y] into bmp
            }
        }
        return bmp;
    }, cancellationToken);
}
Friend Function RenderAsync(data As ImageData, cancellationToken As _
                            CancellationToken) As Task(Of Bitmap)
    Return Task.Run( Function()
                        Dim bmp As New Bitmap(data.Width, data.Height)
                        For y As Integer = 0 to data.Height - 1
                           cancellationToken.ThrowIfCancellationRequested()
                           For x As Integer = 0 To data.Width - 1
                             ' render pixel [x,y] into bmp
                           Next
                        Next
                        Return bmp
                     End Function, cancellationToken)
End Function

Výpočetní úkoly skončí ve stavu Canceled, pokud je splněna alespoň jedna z následujících podmínek:Compute-bound tasks end in a Canceled state if at least one of the following conditions is true:

  • Dříve než úkol přejde do stavu CancellationToken, je požadavek na zrušení doručen prostřednictvím objektu StartNew, který je k dispozici ve formě argumentu metody vytváření (například metody Run nebo Running).A cancellation request arrives through the CancellationToken object, which is provided as an argument to the creation method (for example, StartNew or Run) before the task transitions to the Running state.

  • Výjimka OperationCanceledException zůstane v těle takového úkolu nezpracovaná. Tato výjimka obsahuje stejný token CancellationToken, který je předán úkolu. Tento token dokazuje, že je požadováno zrušení.An OperationCanceledException exception goes unhandled within the body of such a task, that exception contains the same CancellationToken that is passed to the task, and that token shows that cancellation is requested.

Pokud uvnitř těla úkolu zůstane další nezpracovaná výjimka, skončí tento úkol ve stavu Faulted a pokusy o čekání na úkol nebo přístup k výsledku úkolu způsobí vyvolání výjimky.If another exception goes unhandled within the body of the task, the task ends in the Faulted state, and any attempts to wait on the task or access its result causes an exception to be thrown.

I/O – vázané úkolyI/O-bound tasks

Chcete-li vytvořit úkol, pro který by po celou dobu provádění nemělo existovat podkladové vlákno, použijte typ TaskCompletionSource<TResult>.To create a task that should not be directly backed by a thread for the entirety of its execution, use the TaskCompletionSource<TResult> type. Tento typ vystavuje vlastnost Task, která vrací přidruženou instanci typu Task<TResult>.This type exposes a Task property that returns an associated Task<TResult> instance. Životní cyklus tohoto úkolu je řízen metodami typu TaskCompletionSource<TResult>, jako jsou SetResult, SetException, SetCanceled, a jejich variantami TrySet.The life cycle of this task is controlled by TaskCompletionSource<TResult> methods such as SetResult, SetException, SetCanceled, and their TrySet variants.

Předpokládejte, že chcete vytvořit úkol, který bude dokončen po určitém časovém období.Let's say that you want to create a task that will complete after a specified period of time. Například budete chtít odložit aktivitu v uživatelském rozhraní na pozdější dobu.For example, you may want to delay an activity in the user interface. Třída System.Threading.Timer již poskytuje schopnost asynchronního vyvolání delegátu po zadaném časovém intervalu a pomocí typu TaskCompletionSource<TResult> lze umístit typ Task<TResult> před časovač, například:The System.Threading.Timer class already provides the ability to asynchronously invoke a delegate after a specified period of time, and by using TaskCompletionSource<TResult> you can put a Task<TResult> front on the timer, for example:

   public static Task<DateTimeOffset> Delay(int millisecondsTimeout)
   {
       TaskCompletionSource<DateTimeOffset> tcs = null;
       Timer timer = null;

       timer = new Timer(delegate
       {
           timer.Dispose();
           tcs.TrySetResult(DateTimeOffset.UtcNow);
       }, null, Timeout.Infinite, Timeout.Infinite);

       tcs = new TaskCompletionSource<DateTimeOffset>(timer);
       timer.Change(millisecondsTimeout, Timeout.Infinite);
       return tcs.Task;
   }
   Public Function Delay(millisecondsTimeout As Integer) As Task(Of DateTimeOffset) 
       Dim tcs As TaskCompletionSource(Of DateTimeOffset) = Nothing
       Dim timer As Timer = Nothing
       
       timer = New Timer( Sub(obj)
                             timer.Dispose()
                             tcs.TrySetResult(DateTimeOffset.UtcNow)
                          End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)

       tcs = New TaskCompletionSource(Of DateTimeOffset)(timer)
       timer.Change(millisecondsTimeout, Timeout.Infinite)
       Return tcs.Task
   End Function

Počínaje .NET Framework 4,5 je pro tento účel k dispozici Task.Delay metoda a můžete ji použít uvnitř jiné asynchronní metody, například k implementaci smyčky asynchronního cyklického dotazování:Starting with the .NET Framework 4.5, the Task.Delay method is provided for this purpose, and you can use it inside another asynchronous method, for example, to implement an asynchronous polling loop:

public static async Task Poll(Uri url, CancellationToken cancellationToken, 
                              IProgress<bool> progress)
{
    while(true)
    {
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
        bool success = false;
        try
        {
            await DownloadStringAsync(url);
            success = true;
        }
        catch { /* ignore errors */ }
        progress.Report(success);
    }
}
Public Async Function Poll(url As Uri, cancellationToken As CancellationToken, 
                           progress As IProgress(Of Boolean)) As Task
    Do While True
        Await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken)
        Dim success As Boolean = False
        Try
            await DownloadStringAsync(url)
            success = true
        Catch
           ' ignore errors
        End Try   
        progress.Report(success)
    Loop
End Function

Třída TaskCompletionSource<TResult> nemá neobecný protějšek.The TaskCompletionSource<TResult> class doesn't have a non-generic counterpart. Typ Task<TResult> je však odvozen od typu Task, takže obecný objekt TaskCompletionSource<TResult> můžete použít pro vstupně-výstupní metody, které jednoduše vrátí úkol.However, Task<TResult> derives from Task, so you can use the generic TaskCompletionSource<TResult> object for I/O-bound methods that simply return a task. Chcete-li tuto operaci provést, můžete použít prostředek s fiktivním typem TResult (Boolean je dobrá výchozí volba, ale pokud se obáváte, že uživatel typu Task ji bude přetypovávat dolů na typ Task<TResult>, lze namísto toho použít privátní typ TResult).To do this, you can use a source with a dummy TResult (Boolean is a good default choice, but if you're concerned about the user of the Task downcasting it to a Task<TResult>, you can use a private TResult type instead). Například metoda Delay v předchozím příkladu vrátí aktuální čas s výsledným posunem (Task<DateTimeOffset>).For example, the Delay method in the previous example returns the current time along with the resulting offset (Task<DateTimeOffset>). Pokud je výsledná hodnota nepotřebná, může být metoda kódována spíše takto (všimněte si změny návratového typu a změny argumentu na hodnotu TrySetResult):If such a result value is unnecessary, the method could instead be coded as follows (note the change of return type and the change of argument to TrySetResult):

  public static Task<bool> Delay(int millisecondsTimeout)
  {
       TaskCompletionSource<bool> tcs = null;
       Timer timer = null;

       timer = new Timer(delegate
       {
           timer.Dispose();
           tcs.TrySetResult(true);
       }, null, Timeout.Infinite, Timeout.Infinite);

       tcs = new TaskCompletionSource<bool>(timer);
       timer.Change(millisecondsTimeout, Timeout.Infinite);
       return tcs.Task;
  }
  Public Function Delay(millisecondsTimeout As Integer) As Task(Of Boolean)
       Dim tcs As TaskCompletionSource(Of Boolean) = Nothing
       Dim timer As Timer = Nothing

       Timer = new Timer( Sub(obj)
                             timer.Dispose()
                             tcs.TrySetResult(True)
                          End Sub, Nothing, Timeout.Infinite, Timeout.Infinite)

       tcs = New TaskCompletionSource(Of Boolean)(timer)
       timer.Change(millisecondsTimeout, Timeout.Infinite)
       Return tcs.Task
  End Function

Smíšené úlohy vázané na výpočetní výkon a vstupně-výstupní operaceMixed compute-bound and I/O-bound tasks

Asynchronní metody nejsou omezeny pouze na výpočetní nebo vstupně-výstupní operace, ale mohou představovat kombinaci obou metod.Asynchronous methods are not limited to just compute-bound or I/O-bound operations but may represent a mixture of the two. Ve skutečnosti je větší počet asynchronních operací často sloučen do větších smíšených operací.In fact, multiple asynchronous operations are often combined into larger mixed operations. Například metoda RenderAsync v předchozím příkladu provedla výpočetně náročnou operaci za účelem vykreslení obrázku na základě vstupu proměnné imageData.For example, the RenderAsync method in a previous example performed a computationally intensive operation to render an image based on some input imageData. Zdrojem této proměnné imageData může být webová služba s asynchronním přístupem:This imageData could come from a web service that you asynchronously access:

public async Task<Bitmap> DownloadDataAndRenderImageAsync(
    CancellationToken cancellationToken)
{
    var imageData = await DownloadImageDataAsync(cancellationToken);
    return await RenderAsync(imageData, cancellationToken);
}
Public Async Function DownloadDataAndRenderImageAsync(
             cancellationToken As CancellationToken) As Task(Of Bitmap) 
    Dim imageData As ImageData = Await DownloadImageDataAsync(cancellationToken)
    Return Await RenderAsync(imageData, cancellationToken)
End Function

Tento příklad také znázorňuje, jakým způsobem lze jeden token zrušení zřetězit prostřednictvím několika asynchronních operací.This example also demonstrates how a single cancellation token may be threaded through multiple asynchronous operations. Další informace naleznete v části používání zrušení v tématu použití asynchronního vzoru založeného na úlohách.For more information, see the cancellation usage section in Consuming the Task-based Asynchronous Pattern.

Viz také:See also