Implementando o padrão assíncrono baseado em evento

Se você estiver escrevendo uma classe com algumas operações que possam causar atrasos notáveis, considere a opção de fornecer funcionalidade assíncrona Implementando o Padrão assíncrono baseado em evento.

O Padrão assíncrono baseado em evento fornece uma maneira padronizada de empacotar uma classe que tem recursos assíncronos. Se for implementada com classes auxiliares como AsyncOperationManager, sua classe funcionará corretamente em qualquer modelo de aplicativo, incluindo ASP.NET, aplicativos de Console e aplicativos do Windows Forms.

Para obter um exemplo que implementa o Padrão assíncrono baseado em evento, consulte Como implementar um componente compatível com o padrão assíncrono baseado em evento.

Para operações assíncronas simples, você pode obter o componente BackgroundWorker adequado. Para saber mais sobre BackgroundWorker, consulte Como executar uma operação em segundo plano.

A lista a seguir descreve os recursos do padrão assíncrono baseado em evento discutidos neste tópico.

  • Oportunidades para a implementação do Padrão assíncrono baseado em evento

  • Nomenclatura de métodos assíncronos

  • Suporte opcional de cancelamento

  • Suporte opcional da propriedade IsBusy

  • Fornecimento opcional de suporte para relatórios de progresso

  • Fornecimento opcional de suporte para retorno de resultados incrementais

  • Tratamento de parâmetros Out e Ref em métodos

Oportunidades para a implementação do Padrão assíncrono baseado em evento

Considere a implementação do Padrão assíncrono baseado em evento quando:

  • Os clientes de sua classe não precisam de objetos WaitHandle e IAsyncResult disponíveis para operações assíncronas, ou seja, a sondagem e WaitAll ou WaitAny deverão ser compilados pelo cliente.

  • Você quer que as operações assíncronas sejam gerenciadas pelo cliente com o modelo de evento/delegado conhecido.

Qualquer operação é candidata para uma implementação assíncrona, mas aquelas com expectativa de latência longas devem ser consideradas. Operações nas quais os clientes chamam um método e são notificados após a conclusão, sem necessidade de intervenção adicional, são especialmente apropriadas. Também são apropriadas as operações executadas continuamente, notificando periodicamente os clientes sobre o andamento, resultados incrementais ou alterações de estado.

Para saber mais sobre como decidir pelo suporte ao Padrão assíncrono baseado em evento, confira Decidir quando implementar o Padrão assíncrono baseado em evento.

Nomenclatura de métodos assíncronos

Para cada método síncrono MethodName para o qual você deseja fornecer uma contraparte assíncrona:

Defina um método MethodNameAsync que:

  • Retorna void.

  • Usa os mesmos parâmetros que o método MethodName.

  • Aceita várias invocações.

Opcionalmente, defina uma sobrecarga MethodNameAsync, idêntica ao MethodNameAsync, mas com um parâmetro adicional com valor de objeto chamado userState. Faça isso se você estiver preparado para gerenciar várias chamadas simultâneas de seu método. Nesse caso, o valor userState será entregue novamente para todos os manipuladores de eventos a fim de distinguir invocações do método. Você também pode optar por fazer isso simplesmente como um local para armazenar o estado do usuário para recuperação posterior.

Para cada assinatura de método MethodNameAsync separada:

  1. Defina o evento a seguir na mesma classe que o método:

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Defina o representante a seguir e AsyncCompletedEventArgs. Provavelmente, eles serão definidos fora da própria classe, mas no mesmo namespace.

    Public Delegate Sub MethodNameCompletedEventHandler( _
        ByVal sender As Object, _
        ByVal e As MethodNameCompletedEventArgs)
    
    Public Class MethodNameCompletedEventArgs
        Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As MyReturnType
    End Property
    
    public delegate void MethodNameCompletedEventHandler(object sender,
        MethodNameCompletedEventArgs e);
    
    public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
    {
        public MyReturnType Result { get; }
    }
    
    • Certifique-se de que a classe MethodNameCompletedEventArgs expõe seus membros como propriedades somente leitura, e não como campos, pois os campos impedem a associação de dados.

    • Não defina qualquer classe derivada de AsyncCompletedEventArgs para métodos que não produzem resultados. Simplesmente use uma instância do próprio AsyncCompletedEventArgs.

      Observação

      É perfeitamente aceitável, quando apropriada e plausível, a reutilização de delegados e tipos AsyncCompletedEventArgs. Nesse caso, a nomenclatura não será consistente com o nome do método, pois um delegado e AsyncCompletedEventArgs específicos não ficarão vinculados a um único método.

Suporte opcional de cancelamento

Se sua classe oferecer suporte ao cancelamento de operações assíncronas, o cancelamento deverá ser exposto ao cliente, conforme descrito abaixo. Há dois pontos de decisão que precisam ser alcançados antes de definir o suporte ao cancelamento:

  • Sua classe, incluindo acréscimos futuros antecipados, tem apenas uma operação assíncrona que oferece suporte ao cancelamento?
  • As operações assíncronas que oferecem suporte ao cancelamento podem dar suporte a várias operações pendentes? Ou seja, o método MethodNameAsync usa um parâmetro userState e ele permite várias invocações antes da conclusão de alguma?

Use as respostas para essas duas perguntas na tabela abaixo para determinar qual deve ser a assinatura para o método de cancelamento.

Visual Basic

Várias operações simultâneas com suporte Apenas uma operação por vez
Uma operação assíncrona em toda a classe Sub MethodNameAsyncCancel(ByVal userState As Object) Sub MethodNameAsyncCancel()
Várias operações assíncronas em classe Sub CancelAsync(ByVal userState As Object) Sub CancelAsync()

C#

Várias operações simultâneas com suporte Apenas uma operação por vez
Uma operação assíncrona em toda a classe void MethodNameAsyncCancel(object userState); void MethodNameAsyncCancel();
Várias operações assíncronas em classe void CancelAsync(object userState); void CancelAsync();

Se você definir o método CancelAsync(object userState), os clientes deverão ter cuidado ao escolher seus valores de estado a fim de torná-los capazes de distinguir entre todos os métodos assíncronos invocados no objeto, e não apenas entre todas as invocações de um único método assíncrono.

A decisão de nomear a versão de operação assíncrona única MethodNameAsyncCancel tem base em ser capaz de descobrir mais facilmente o método em um ambiente de design como IntelliSense do Visual Studio. Isso agrupa os membros relacionados e os distingue de outros membros que não têm qualquer relação com funcionalidade assíncrona. Se você espera que outras operações assíncronas sejam adicionadas em versões subsequentes, é melhor definir CancelAsync.

Não defina vários métodos da tabela acima na mesma classe. Isso não fará sentido, ou sobrecarregará a interface de classe com a proliferação de métodos.

Normalmente, esses métodos retornarão imediatamente, e a operação poderá ou não ser realmente cancelada. No manipulador de eventos do evento MethodNameCompleted, o objeto MethodNameCompletedEventArgs contém um campo Cancelled, o qual os clientes podem usar para determinar se o cancelamento ocorreu.

Obedeça à semântica de cancelamento descrita em Melhores práticas para implementar o Padrão assíncrono baseado em evento.

Suporte opcional da propriedade IsBusy

Se sua classe não oferecer suporte a várias invocações simultâneas, considere a exposição de uma propriedade IsBusy. Isso permite que os desenvolvedores determinem se um método MethodNameAsync está sendo executado sem capturar uma exceção do método MethodNameAsync.

Obedeça à semântica IsBusy descrita em Melhores práticas para implementar o Padrão assíncrono baseado em evento.

Fornecimento opcional de suporte para relatórios de progresso

Costuma ser bom relatar o progresso durante uma operação assíncrona. O padrão assíncrono baseado em evento fornece uma orientação para fazer isso.

  • Opcionalmente, defina um evento que será gerado pela operação assíncrona e invocado no thread apropriado. O objeto ProgressChangedEventArgs transporta um indicador de progresso com valor de inteiro que deve estar entre 0 e 100.

  • Nomeie esse evento da seguinte maneira:

    • ProgressChanged se a classe tiver várias operações assíncronas (ou exista a expectativa de crescimento a fim de incluir várias operações assíncronas em versões futuras);

    • MethodNameProgressChanged se a classe tiver uma única operação assíncrona.

    Essa opção de nomenclatura é comparável à feita para o método de cancelamento, conforme descrito na seção Suporte opcional de cancelamento.

Esse evento deve usar a assinatura do delegado ProgressChangedEventHandler e a classe ProgressChangedEventArgs. Como alternativa, se um indicador de progresso mais específico ao domínio puder ser fornecido (por exemplo, bytes lidos e total de bytes de uma operação de download), você deverá definir uma classe derivada de ProgressChangedEventArgs.

Observe que há apenas um evento ProgressChanged ou MethodNameProgressChanged para a classe, independentemente do número de métodos assíncronos para os quais ela dá suporte. Os clientes devem usar o objeto userState que é passado para os métodos MethodNameAsync a fim de distinguir entre as atualizações de andamento em várias operações simultâneas.

Pode haver situações em que várias operações dão suporte ao progresso e cada uma retorna um indicador diferente para o progresso. Nesse caso, um único ProgressChanged evento não é apropriado, e você pode considerar o suporte a vários eventos ProgressChanged. Nesse caso, use um padrão de nomenclatura de MethodNameProgressChanged para cada método MethodNameAsync.

Obedeça à semântica de relatório de progresso descrita em Melhores práticas para implementar o Padrão assíncrono baseado em evento.

Fornecimento opcional de suporte para retorno de resultados incrementais

Às vezes, uma operação assíncrona pode retornar resultados incrementais antes da conclusão. Há várias opções que podem ser usadas para oferecer suporte a esse cenário. Veja a seguir alguns exemplos.

Classe de operação única

Se sua classe der suporte apenas a uma única operação assíncrona, e essa operação for capaz de retornar resultados incrementais, então:

  • Estenda o tipo ProgressChangedEventArgs para carregar os dados de resultado incrementais e definir um evento MethodNameProgressChanged com esses dados estendidos.

  • Gere este evento MethodNameProgressChanged quando houver um resultado incremental ao relatório.

Essa solução aplica-se especificamente a uma classe de operação assíncrona única, pois não há nenhum problema com o mesmo evento ocorrendo para retornar resultados incrementais em "todas as operações", como faz o evento MethodNameProgressChanged.

Classe de várias operações com resultados incrementais homogêneos

Nesse caso, sua classe oferece suporte a vários métodos assíncronos, cada um capaz de retornar resultados incrementais, e todos esses resultados incrementais têm o mesmo tipo de dados.

Siga o modelo descrito acima para classes de operação única, pois a mesma estrutura EventArgs funcionará para todos os resultados incrementais. Defina um evento ProgressChanged em vez de um evento MethodNameProgressChanged, já que ele se aplica a vários métodos assíncronos.

Classe de várias operações com resultados incrementais heterogêneos

Se sua classe oferece suporte a vários métodos assíncronos, cada um retornando um tipo diferente de dados, você deve:

  • Separar o relatório de resultado incremental de seu relatório de progresso.

  • Definir um evento MethodNameProgressChanged separado com o EventArgs apropriado para cada método assíncrono manipular dados de resultados incrementais desse método.

Invocar esse manipulador de eventos no thread apropriado, conforme descrito em Melhores práticas para implementar o Padrão assíncrono baseado em evento.

Tratamento de parâmetros Out e Ref em métodos

Embora o uso de out e ref seja, em geral, desencorajado no .NET, estas são as regras quando eles estiverem presentes:

Em um método síncrono MethodName:

  • Os parâmetros out para MethodName não devem fazer parte de MethodNameAsync. Em vez disso, devem fazer parte de MethodNameCompletedEventArgs com o mesmo nome que seu parâmetro equivalente em MethodName (a menos que haja um nome mais apropriado).

  • Os parâmetros ref para MethodName devem aparecer como parte de MethodNameAsync e como parte de MethodNameCompletedEventArgs com o mesmo nome que seu parâmetro equivalente em MethodName (a menos que haja um nome mais apropriado).

Por exemplo, considerando que:

Public Function MethodName(ByVal arg1 As String, ByRef arg2 As String, ByRef arg3 As String) As Integer
public int MethodName(string arg1, ref string arg2, out string arg3);

Seu método assíncrono e sua classe AsyncCompletedEventArgs teria esta aparência:

Public Sub MethodNameAsync(ByVal arg1 As String, ByVal arg2 As String)

Public Class MethodNameCompletedEventArgs
    Inherits System.ComponentModel.AsyncCompletedEventArgs
    Public ReadOnly Property Result() As Integer
    End Property
    Public ReadOnly Property Arg2() As String
    End Property
    Public ReadOnly Property Arg3() As String
    End Property
End Class
public void MethodNameAsync(string arg1, string arg2);

public class MethodNameCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs
{
    public int Result { get; };
    public string Arg2 { get; };
    public string Arg3 { get; };
}

Confira também