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.
No .NET, o padrão assíncrono baseado em tarefa é o padrão de design assíncrono recomendado para o novo desenvolvimento. Ele é baseado nos tipos Task e Task<TResult> no System.Threading.Tasks namespace, que são usados para representar operações assíncronas.
Nomenclatura, parâmetros e tipos de retorno
O TAP usa um único método para representar a iniciação e a conclusão de uma operação assíncrona. Isso contrasta com o padrão de Modelo de Programação Assíncrono (APM ou IAsyncResult
) e o Padrão Assíncrono Baseado em Evento (EAP). O APM requer Begin
e End
métodos. O EAP requer um método que tenha o sufixo Async
e também requer um ou mais eventos, tipos delegados de manipulador de eventos e tipos derivados de EventArg
. Os métodos assíncronos no TAP incluem o Async
sufixo após o nome da operação para métodos que retornam tipos aguardados, como Task, Task<TResult>, ValueTaske ValueTask<TResult>. Por exemplo, uma operação assíncrona Get
que retorna um Task<String>
pode ser nomeada GetAsync
. Se você estiver adicionando um método TAP a uma classe que já contém um nome de método EAP com o sufixo Async
, use o sufixo TaskAsync
. Por exemplo, se a classe já tiver um GetAsync
método, use o nome GetTaskAsync
. Se um método iniciar uma operação assíncrona, mas não retornar um tipo esperável, seu nome deverá começar com Begin
, Start
ou algum outro verbo para sugerir que esse método não retorne ou lance o resultado da operação.
Um método TAP retorna um System.Threading.Tasks.Task ou um System.Threading.Tasks.Task<TResult>, com base em se o método síncrono correspondente retorna nulo ou um tipo TResult
.
Os parâmetros de um método TAP devem corresponder aos parâmetros de seu equivalente síncrono e devem ser fornecidos na mesma ordem. No entanto, out
e ref
os parâmetros são isentos dessa regra e devem ser totalmente evitados. Todos os dados que teriam sido retornados por meio de um out
ou ref
parâmetro devem, em vez disso, ser retornados como parte do TResult
retornado por Task<TResult>, e devem usar uma tupla ou uma estrutura de dados personalizada para acomodar vários valores. Além disso, considere adicionar um CancellationToken parâmetro mesmo que o equivalente síncrono do método TAP não ofereça um.
Métodos dedicados exclusivamente à criação, manipulação ou combinação de tarefas (em que a intenção assíncrona do método é clara no nome do método ou no nome do tipo ao qual o método pertence) não precisam seguir esse padrão de nomenclatura; esses métodos geralmente são chamados de combinadores. Exemplos de combinadores incluem WhenAll e WhenAny, e são discutidos na seção Usando os combinadores internos baseados em tarefas do artigo Consumindo o padrão assíncrono baseado em tarefas.
Para obter exemplos de como a sintaxe TAP difere da sintaxe usada em padrões de programação assíncrona herdados, como o Modelo de Programação Assíncrona (APM) e o Padrão Assíncrono Baseado em Evento (EAP), consulte Padrões de Programação Assíncrono.
Iniciando uma operação assíncrona
Um método assíncrono baseado em TAP pode fazer uma pequena quantidade de trabalho de forma síncrona, como validar argumentos e iniciar a operação assíncrona, antes de retornar a tarefa resultante. O trabalho síncrono deve ser mantido no mínimo para que o método assíncrono possa retornar rapidamente. Os motivos para um retorno rápido incluem:
Métodos assíncronos podem ser invocados a partir de threads da interface do usuário (UI) e qualquer trabalho síncrono de execução prolongada pode prejudicar a capacidade de resposta do aplicativo.
Vários métodos assíncronos podem ser iniciados simultaneamente. Portanto, qualquer trabalho de execução longa na parte síncrona de um método assíncrono pode atrasar o início de outras operações assíncronas, diminuindo assim os benefícios da simultaneidade.
Em alguns casos, a quantidade de trabalho necessária para concluir a operação é menor do que a quantidade de trabalho necessária para iniciar a operação de forma assíncrona. A leitura de um fluxo em que a operação de leitura pode ser atendida por dados que já estão armazenados em buffer na memória é um exemplo desse cenário. Nesses casos, a operação pode ser concluída de forma síncrona e pode retornar uma tarefa que já foi concluída.
Exceções
Um método assíncrono deve gerar uma exceção para ser lançada fora da chamada do método assíncrono somente em resposta a um erro de uso. Erros de uso nunca devem ocorrer no código de produção. Por exemplo, se passar uma referência nula (Nothing
no Visual Basic) como um dos argumentos do método causar um estado de erro (geralmente representado por uma ArgumentNullException exceção), você poderá modificar o código de chamada para garantir que uma referência nula nunca seja passada. Para todos os outros erros, exceções que ocorrem quando um método assíncrono está em execução devem ser atribuídas à tarefa retornada, mesmo que o método assíncrono seja concluído de forma síncrona antes que a tarefa seja retornada. Normalmente, uma tarefa contém no máximo uma exceção. No entanto, se a tarefa representar várias operações (por exemplo, WhenAll), várias exceções poderão ser associadas a uma única tarefa.
Ambiente de destino
Ao implementar um método TAP, você pode determinar onde ocorre a execução assíncrona. Você pode optar por executar a carga de trabalho no pool de threads, implementá-la usando E/S assíncrona (sem estar associado a um thread durante a maior parte da execução da operação), executá-la em um thread específico (como o thread da interface do usuário) ou usar qualquer número de contextos potenciais. Um método TAP pode até não ter nada para executar e pode retornar apenas um Task que representa a ocorrência de uma condição em outro lugar do sistema (por exemplo, uma tarefa que representa os dados que chegam a uma estrutura de dados na fila).
O chamador do método TAP pode bloquear a espera para o método TAP ser concluído com a espera síncrona na tarefa resultante ou pode executar código adicional (continuação) quando a operação assíncrona é concluída. O criador do código de continuação tem controle sobre onde esse código é executado. Você pode criar o código de continuação explicitamente, por meio de métodos na Task classe (por exemplo, ContinueWith) ou implicitamente, usando o suporte de linguagem baseado em continuações (por exemplo, await
em C#, Await
no Visual Basic, AwaitValue
em F#).
Status da tarefa
A Task classe fornece um ciclo de vida para operações assíncronas e esse ciclo é representado pela TaskStatus enumeração. Para dar suporte a casos de canto de tipos derivados de Task e Task<TResult> e à separação da construção do agendamento, a classe Task expõe um método Start. As tarefas que são criadas pelos construtores públicos Task são chamadas de tarefas frias, porque começam seu ciclo de vida no estado não agendado Created e são agendadas somente quando Start é chamado nessas instâncias.
Todas as outras tarefas iniciam seu ciclo de vida em um estado ativo, o que significa que as operações assíncronas que representam já foram iniciadas e seu status de tarefa é um valor de enumeração diferente de TaskStatus.Created. Todas as tarefas que são retornadas dos métodos TAP devem ser ativadas. Se um método TAP usar internamente o construtor de uma tarefa para instanciar a tarefa a ser retornada, o método TAP precisa chamar Start no objeto Task antes de retorná-lo. Os consumidores de um método TAP podem presumir com segurança que a tarefa retornada está ativa e não devem tentar chamar Start em qualquer Task que é retornado de um método TAP. Chamar Start em uma tarefa ativa resulta em uma exceção InvalidOperationException.
Cancelamento (opcional)
No TAP, o cancelamento é opcional para implementadores de método assíncrono e consumidores de método assíncrono. Se uma operação permitir cancelamento, ela exporá uma sobrecarga do método assíncrono que aceita um token de cancelamento (CancellationToken instância). Por convenção, o parâmetro é nomeado 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
A operação assíncrona monitora esse token para solicitações de cancelamento. Se receber uma solicitação de cancelamento, ele poderá optar por atender a essa solicitação e cancelar a operação. Se a solicitação de cancelamento resultar no término prematuro do trabalho, o método TAP retornará uma tarefa que termina no Canceled estado; não há nenhum resultado disponível e nenhuma exceção é gerada. O estado Canceled é considerado um estado final (concluído) para uma tarefa, juntamente com os estados Faulted e RanToCompletion. Portanto, se uma tarefa estiver no Canceled estado, sua IsCompleted propriedade retornará true
. Quando uma tarefa é concluída no estado Canceled, quaisquer continuações registradas com a tarefa são programadas ou executadas, a menos que uma opção de continuação, como NotOnCanceled, tenha sido especificada para recusar a continuação. Qualquer código que esteja aguardando de forma assíncrona uma tarefa cancelada por meio do uso de recursos de linguagem continua a ser executado, mas recebe uma OperationCanceledException ou uma exceção derivada dela. Código que está bloqueado de forma síncrona aguardando a tarefa por meio de métodos como Wait e WaitAll também continua a ser executado com uma exceção.
Se um token de cancelamento tiver solicitado o cancelamento antes do método TAP que aceita esse token ser chamado, o método TAP deverá retornar uma Canceled tarefa. No entanto, se o cancelamento for solicitado enquanto a operação assíncrona estiver em execução, a operação assíncrona não precisará aceitar a solicitação de cancelamento. A tarefa retornada deve terminar no Canceled estado somente se a operação terminar como resultado da solicitação de cancelamento. Se o cancelamento for solicitado, mas um resultado ou uma exceção ainda for produzido, a tarefa deverá terminar no estado RanToCompletion ou Faulted.
Para métodos assíncronos que expõem a capacidade de cancelamento em primeiro lugar, não é necessário fornecer uma sobrecarga que não aceite um token de cancelamento. Para métodos que não podem ser cancelados, não forneça sobrecargas que aceitem um token de cancelamento; isso ajuda a indicar ao chamador se o método de destino é realmente cancelável. O código consumidor que não deseja cancelamento pode chamar um método que aceita um CancellationToken e fornece None como o valor do argumento. None é funcionalmente equivalente ao padrão CancellationToken.
Relatórios de progresso (opcional)
Algumas operações assíncronas se beneficiam do fornecimento de notificações de progresso; normalmente, elas são usadas para atualizar uma interface do usuário com informações sobre o progresso da operação assíncrona.
No TAP, o progresso é tratado por meio de uma IProgress<T> interface, que é passada para o método assíncrono como um parâmetro geralmente chamado de progress
. Fornecer a interface de progresso quando o método assíncrono é chamado ajuda a eliminar as condições de corrida resultantes do uso incorreto (isto é, quando os manipuladores de eventos registrados incorretamente depois que a operação é iniciada podem carecer de atualizações). Mais importante, a interface de progresso suporta implementações de variação de progresso, conforme determinado pelo código consumidor. Por exemplo, o código consumidor pode se preocupar somente com a atualização de progresso mais recente, armazenar em buffer todas as atualizações, invocar uma ação para cada atualização ou controlar se a invocação passou por marshalling para um segmento específico. Todas essas opções podem ser obtidas usando uma implementação diferente da interface, personalizada de acordo com as necessidades do consumidor específico. Assim como ocorre com o cancelamento, as implementações de TAP devem fornecer um IProgress<T> parâmetro somente se a API der suporte a notificações de progresso.
Por exemplo, se o método ReadAsync
, discutido anteriormente neste artigo, for capaz de relatar o progresso intermediário por meio do número de bytes lidos até o momento, a chamada de retorno de progresso poderá ser uma interface IProgress<T>.
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
Se um método FindFilesAsync
retornar uma lista de todos os arquivos que atendem a um padrão de pesquisa específico, o retorno de chamada de progresso poderá fornecer uma estimativa da porcentagem de trabalho concluído e o conjunto atual de resultados parciais. Ele pode fornecer essas informações com uma tupla:
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))
ou com um tipo de dados específico para a 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))
No último caso, o tipo de dados especial geralmente tem o sufixo ProgressInfo
.
Se as implementações TAP fornecerem sobrecargas que aceitam um progress
parâmetro, elas deverão permitir que o argumento seja null
, nesse caso, nenhum progresso é relatado. As implementações TAP devem relatar o progresso para o objeto Progress<T> de forma síncrona, o que permite que o método assíncrono forneça rapidamente o progresso. Ele também permite que o consumidor do progresso determine como e onde melhor lidar com as informações. Por exemplo, a instância de progresso poderia optar por controlar retornos de chamada e gerar eventos em um contexto de sincronização capturado.
Implementações do IProgress<T>
O .NET fornece a Progress<T> classe, que implementa IProgress<T>. A Progress<T> classe é declarada da seguinte maneira:
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T>? ProgressChanged;
}
Uma instância de Progress<T> expõe um evento ProgressChanged, o qual é gerado sempre que a operação assíncrona relata uma atualização de progresso. O evento ProgressChanged é gerado no objeto de SynchronizationContext que foi capturado quando a instância de Progress<T> foi criada. Se nenhum contexto de sincronização estiver disponível, um contexto padrão direcionado ao pool de threads será usado. É possível registrar manipuladores com esse evento. Um único manipulador também pode ser fornecido ao Progress<T> construtor para conveniência e se comporta como um manipulador de eventos para o ProgressChanged evento. As atualizações de progresso são geradas de forma assíncrona para evitar atrasar a operação assíncrona enquanto os manipuladores de eventos estão em execução. Outra IProgress<T> implementação poderia optar por aplicar semântica diferente.
Escolhendo as sobrecargas a serem fornecidas
Se uma implementação TAP utilizar tanto os parâmetros opcionais CancellationToken quanto IProgress<T>, ela poderá exigir até quatro sobrecargas.
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
No entanto, muitas implementações de TAP não fornecem recursos de cancelamento ou progresso, portanto, elas exigem um único método:
public Task MethodNameAsync(…);
Public MethodNameAsync(…) As Task
Se uma implementação de TAP oferecer suporte a cancelamento ou progresso, mas não a ambos, ela poderá fornecer duas sobrecargas:
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
Se uma implementação de TAP oferecer suporte a cancelamento e progresso, ela poderá expor todas as quatro sobrecargas. No entanto, ele pode fornecer apenas os dois seguintes:
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
Para compensar as duas combinações intermediárias ausentes, os desenvolvedores podem passar None ou um padrão CancellationToken para o cancellationToken
parâmetro e null
para o progress
parâmetro.
Para que todo uso do método TAP dê suporte a cancelamentos ou progressos, omita as sobrecargas que não aceitam o parâmetro relevante.
Para expor diversas sobrecargas a fim de tornar o cancelamento ou o progresso opcional, as sobrecargas que não dão suporte ao cancelamento ou ao progresso devem se comportar como se tivessem transmitido None para cancelamento ou null
para progresso à sobrecarga que dá suporte a eles.
Artigos relacionados
Título | Descrição |
---|---|
Padrões de programação assíncrona | Apresenta os três padrões para executar operações assíncronas: o TAP (Padrão Assíncrono baseado em tarefa), o APM (Modelo de Programação Assíncrona) e o Padrão Assíncrono baseado em evento (EAP). |
Implementando o padrão assíncrono baseado em tarefa | Descreve como implementar o TAP (Padrão Assíncrono baseado em tarefa) de três maneiras: usando os compiladores C# e Visual Basic no Visual Studio manualmente ou por meio de uma combinação do compilador e dos métodos manuais. |
Consumindo o padrão assíncrono baseado em tarefa | Descreve como você pode usar tarefas e retornos de chamada para implementar a espera sem causar bloqueios. |
Interoperabilidade com outros padrões e tipos assíncronos | Descreve como usar o TAP (padrão assíncrono baseado em tarefa) para implementar o Modelo de Programação Assíncrona (APM) e o Padrão Assíncrono Baseado em Evento (EAP). |