Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Você pode implementar o TAP (padrão assíncrono baseado em tarefa) de três maneiras: usando os compiladores C# e Visual Basic em Visual Studio, manualmente ou por meio de uma combinação do compilador e dos métodos manuais. As seções a seguir discutem cada método em detalhes. Você pode usar o padrão TAP para implementar operações assíncronas limitadas pela computação e por operações de entrada/saída. A seção Cargas de Trabalho discute cada tipo de operação.
Gerando métodos TAP
Usando os compiladores
A partir do .NET Framework 4.5, qualquer método atribuído com a palavra-chave async (Async em Visual Basic) é considerado um método assíncrono. Os compiladores C# e Visual Basic executam as transformações necessárias para implementar o método de forma assíncrona usando TAP. Um método assíncrono deve retornar um System.Threading.Tasks.Task ou um System.Threading.Tasks.Task<TResult> objeto. Para este último, o corpo da função deve retornar um TResult, e o compilador garante que esse resultado seja disponibilizado por meio do objeto de tarefa resultante. Da mesma forma, quaisquer exceções que não sejam tratadas no corpo do método são encaminhadas para a tarefa de saída e fazem com que a tarefa resultante termine no estado TaskStatus.Faulted. A exceção a essa regra é quando um OperationCanceledException (ou tipo derivado) fica sem tratamento, nesse caso, a tarefa resultante termina no TaskStatus.Canceled estado.
Task.Start e eliminação de tarefas
Use Start apenas para tarefas criadas explicitamente com um construtor Task que ainda estão no estado Created. Os métodos TAP públicos devem retornar tarefas ativas, portanto, os chamadores não devem precisar chamar Start.
Na maioria dos códigos de programação TAP, não descarte tarefas. No caso típico, um Task não contém recursos não gerenciados, e a eliminação de cada tarefa adiciona uma sobrecarga sem benefício prático. Descarte somente quando APIs ou medições específicas indicarem necessidade.
Se você iniciar uma atividade em segundo plano que sobreviva à sequência de chamadas imediata, mantenha a propriedade explícita e acompanhe a conclusão. Para obter mais diretrizes, consulte Manter os métodos assíncronos vivos.
Gerando métodos TAP manualmente
Você pode implementar o padrão TAP manualmente para melhor controle sobre a implementação. O compilador depende da área pública exposta pelo namespace System.Threading.Tasks e pelos tipos de suporte no namespace System.Runtime.CompilerServices. Para implementar o TAP por conta própria, crie um objeto TaskCompletionSource<TResult>, execute a operação assíncrona e, quando for concluída, chame o método SetResult, SetException ou SetCanceled, ou a versão Try de um destes métodos. Ao implementar um método TAP manualmente, você deve concluir a tarefa resultante quando a operação assíncrona representada for concluída. Por exemplo:
static class StreamExtensions
{
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;
}
}
Module StreamExtensions
<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
End Module
Abordagem híbrida
Você pode achar útil implementar o padrão TAP manualmente, mas delegar a lógica principal da implementação ao compilador. Por exemplo, talvez você queira usar a abordagem híbrida quando quiser verificar argumentos fora de um método assíncrono gerado pelo compilador para que as exceções possam escapar para o chamador direto do método em vez de serem expostas por meio do System.Threading.Tasks.Task objeto:
class Calculator
{
private int value = 0;
public Task<int> MethodAsync(string input)
{
if (input == null) throw new ArgumentNullException(nameof(input));
return MethodAsyncInternal(input);
}
private async Task<int> MethodAsyncInternal(string input)
{
// code that uses await goes here
await Task.Delay(1);
return value;
}
}
Class Calculator
Private value As Integer = 0
Public Function MethodAsync(input As String) As Task(Of Integer)
If input Is Nothing Then Throw New ArgumentNullException(NameOf(input))
Return MethodAsyncInternal(input)
End Function
Private Async Function MethodAsyncInternal(input As String) As Task(Of Integer)
' code that uses await goes here
Await Task.Delay(1)
Return value
End Function
End Class
Outro caso em que essa delegação é útil é quando você está implementando a otimização de caminho rápido e deseja retornar uma tarefa armazenada em cache.
Cargas de trabalho
Você pode implementar operações assíncronas associadas à computação e de E/S como métodos TAP. No entanto, quando você expõe métodos TAP publicamente de uma biblioteca, forneça-os apenas para cargas de trabalho que envolvam operações associadas a E/S. Essas operações também podem envolver computação, mas não devem ser puramente computacionais. Se um método for puramente associado à computação, exponha-o apenas como uma implementação síncrona. O código que o consome pode escolher se deseja encapsular uma invocação desse método síncrono em uma tarefa para descarregar o trabalho para outro thread ou para alcançar o paralelismo. Se um método for associado a E/S, exponha-o apenas como uma implementação assíncrona.
Tarefas associadas à computação
A System.Threading.Tasks.Task classe funciona bem para representar operações computacionalmente intensivas. Por padrão, ele aproveita o suporte especial dentro da ThreadPool classe para fornecer uma execução eficiente. Ele também fornece controle significativo sobre quando, onde e como os cálculos assíncronos são executados.
Gere tarefas associadas à computação das seguintes maneiras:
No .NET Framework 4.5 e versões posteriores (incluindo .NET Core e .NET 5+), use o método estático Task.Run como um atalho para TaskFactory.StartNew. Use Run para iniciar facilmente uma tarefa de computação intensiva que utiliza o pool de threads. Esse método é o mecanismo preferencial para iniciar uma tarefa associada à computação. Use
StartNewdiretamente somente quando quiser um controle mais refinado sobre a tarefa.No .NET Framework 4, use o método TaskFactory.StartNew. Ele aceita um delegado (normalmente um Action<T> ou um Func<TResult>) para ser executado de forma assíncrona. Se você fornecer um Action<T> delegado, o método retornará um System.Threading.Tasks.Task objeto que representa a execução assíncrona desse delegado. Se você fornecer um Func<TResult> delegado, o método retornará um System.Threading.Tasks.Task<TResult> objeto. Sobrecargas do StartNew método aceitam um token de cancelamento (CancellationToken), opções de criação de tarefa (TaskCreationOptions) e um agendador de tarefas (TaskScheduler). Esses parâmetros fornecem controle refinado sobre o agendamento e a execução da tarefa. Uma instância de fábrica direcionada ao agendador de tarefas atual está disponível como uma propriedade estática (Factory) da Task classe. Por exemplo:
Task.Factory.StartNew(…).Use os construtores do
Tasktipo e oStartmétodo se você quiser gerar e agendar a tarefa separadamente. Os métodos públicos só devem retornar tarefas que já foram iniciadas.Use as sobrecargas do método Task.ContinueWith. Esse método cria uma nova tarefa agendada quando outra tarefa é concluída. Algumas das sobrecargas ContinueWith aceitam um token de cancelamento, opções de continuação e um agendador de tarefas para obter melhor controle sobre o agendamento e a execução da tarefa de continuação.
Use os métodos TaskFactory.ContinueWhenAll e TaskFactory.ContinueWhenAny. Esses métodos criam uma nova tarefa agendada quando todo ou qualquer um dos conjuntos de tarefas fornecido é concluído. Esses métodos também fornecem sobrecargas para controlar o agendamento e a execução dessas tarefas.
Em tarefas associadas à computação, o sistema poderá impedir a execução de uma tarefa agendada se receber uma solicitação de cancelamento antes de começar a executar a tarefa. Dessa forma, se você fornecer um token de cancelamento (CancellationToken objeto), poderá passar esse token para o código assíncrono que monitora o token. Você também pode fornecer o token a um dos métodos mencionados anteriormente, como StartNew ou Run para que o Task runtime também possa monitorar o token.
Por exemplo, considere um método assíncrono que renderiza uma imagem. O corpo da tarefa pode consultar o token de cancelamento para que o código seja encerrado mais cedo se uma solicitação de cancelamento chegar durante a renderização. Além disso, se a solicitação de cancelamento chegar antes do início da renderização, você deseja impedir a operação de renderização:
internal static 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
Note
Este exemplo usa Bitmap, que requer o pacote System.Drawing.Common e tem suporte apenas em Windows. O padrão de tarefa intensivo em computação — usando Task.Run com um CancellationToken — aplica-se a todas as plataformas; substitua uma biblioteca de imagens multiplataforma para alvos que não são Windows.
As tarefas associadas à computação terminam em um Canceled estado se pelo menos uma das seguintes condições for verdadeira:
Uma solicitação de cancelamento chega por meio do CancellationToken objeto, que é fornecido como um argumento para o método de criação (por exemplo,
StartNewouRun) antes que a tarefa faça a transição para o Running estado.Uma exceção OperationCanceledException permanece sem tratamento dentro do corpo de tal tarefa. Essa exceção contém o mesmo CancellationToken que é passado para a tarefa, e esse token indica que o cancelamento é solicitado.
Se outra exceção não for tratada no corpo da tarefa, a tarefa terminará no Faulted estado. Qualquer tentativa de aguardar a tarefa ou acessar seu resultado faz com que uma exceção seja gerada.
Tarefas associadas à E/S
Para criar uma tarefa que não deve usar diretamente um thread para toda a execução, use o TaskCompletionSource<TResult> tipo. Esse tipo expõe uma Task propriedade que retorna uma instância associada Task<TResult> . Você controla o ciclo de vida dessa tarefa usando TaskCompletionSource<TResult> métodos tais como SetResult, SetException, SetCanceled e suas TrySet variantes.
Suponha que você queira criar uma tarefa que seja concluída após um período de tempo especificado. Por exemplo, talvez você queira atrasar uma atividade na interface do usuário. A System.Threading.Timer classe já fornece a capacidade de invocar de forma assíncrona um delegado após um período de tempo especificado. Usando TaskCompletionSource<TResult>, você pode colocar uma Task<TResult> na frente do temporizador. Por exemplo:
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
O Task.Delay método é fornecido para essa finalidade. Você pode usá-lo dentro de outro método assíncrono, por exemplo, para implementar um loop de sondagem assíncrono:
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
A TaskCompletionSource<TResult> classe não tem um equivalente não genérico. No entanto, Task<TResult> deriva de Task, para que você possa usar o objeto genérico TaskCompletionSource<TResult> para métodos associados a E/S que simplesmente retornam uma tarefa. Para fazer isso, use uma fonte com um simulado TResult (Boolean é um padrão recomendado, mas se você estiver preocupado com o usuário do Task converter para um tipo inferior Task<TResult>, poderá usar um tipo privado TResult em vez disso). Por exemplo, o Delay método no exemplo anterior retorna o tempo atual junto com o deslocamento resultante (Task<DateTimeOffset>). Se esse valor de resultado for desnecessário, o método poderá ser codificado da seguinte maneira (observe a alteração do tipo de retorno e a alteração do argumento para TrySetResult):
public static Task<bool> DelaySimple(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 DelaySimple(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
Tarefas de computação mistas envolvendo processamento e E/S
Os métodos assíncronos não se limitam apenas a operações associadas à computação ou de E/S. Eles podem representar uma mistura dos dois. Na verdade, você geralmente combina várias operações assíncronas em operações mistas maiores. Por exemplo, o RenderAsync método em um exemplo anterior executa uma operação computacionalmente intensiva para renderizar uma imagem com base em alguma entrada imageData. Isso imageData pode vir de um serviço Web que você acessa de forma assíncrona:
public static 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
Note
Este exemplo usa Bitmap, que requer o pacote System.Drawing.Common e tem suporte apenas em Windows. O padrão de encadeamento de um download assíncrono com uma operação associada à computação assíncrona se aplica a todas as plataformas; substitua uma biblioteca de imagens multiplataforma por destinos não Windows.
Este exemplo também demonstra como um único token de cancelamento pode ser encadeado por meio de várias operações assíncronas. Para saber mais, veja a seção de uso de cancelamento em Consumindo o padrão assíncrono baseado em tarefa.