Share via


Implementando o padrão assíncrono baseado em eventos

Se você estiver escrevendo uma classe com algumas operações que podem incorrer em atrasos percetíveis, considere dar-lhe funcionalidade assíncrona implementando o padrão assíncrono baseado em eventos.

O padrão assíncrono baseado em evento fornece uma maneira padronizada de empacotar uma classe que tem recursos assíncronos. Se 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 que ofereça suporte ao padrão assíncrono baseado em evento.

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

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

  • Oportunidades para implementar o padrão assíncrono baseado em eventos

  • Nomeando métodos assíncronos

  • Opcionalmente Suporte Cancelamento

  • Opcionalmente, ofereça suporte à propriedade IsBusy

  • Opcionalmente, fornecer suporte para relatórios de progresso

  • Opcionalmente, fornecer suporte para retornar resultados incrementais

  • Manipulando parâmetros de saída e ref em métodos

Oportunidades para implementar o padrão assíncrono baseado em eventos

Considere implementar o padrão assíncrono baseado em eventos quando:

  • Os clientes da sua classe não precisam WaitHandle e IAsyncResult os objetos estão disponíveis para operações assíncronas, o que significa que a sondagem e WaitAll /ou WaitAny precisará ser criada pelo cliente.

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

Qualquer operação é candidata a uma implementação assíncrona, mas aquelas que você espera incorrer em latências longas devem ser consideradas. Especialmente apropriadas são as operações em que os clientes chamam um método e são notificados após a conclusão, sem necessidade de intervenção adicional. Também são apropriadas as operações que são executadas continuamente, notificando periodicamente os clientes sobre progresso, resultados incrementais ou alterações de estado.

Para obter mais informações sobre como decidir quando dar suporte ao padrão assíncrono baseado em evento, consulte Decidindo quando implementar o padrão assíncrono baseado em evento.

Nomeando 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:

  • Devoluções void.

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

  • Aceita várias invocações.

Opcionalmente, defina uma sobrecarga MethodNameAsync , idêntica a MethodNameAsync, mas com um parâmetro adicional com valor de objeto chamado userState. Faça isso se você estiver preparado para gerenciar várias invocações simultâneas do seu método, caso em que o userState valor será entregue de volta a todos os manipuladores de eventos para 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 seguinte evento na mesma classe que o método:

    Public Event MethodNameCompleted As MethodNameCompletedEventHandler
    
    public event MethodNameCompletedEventHandler MethodNameCompleted;
    
  2. Defina o seguinte delegado e AsyncCompletedEventArgs. Eles provavelmente 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 exponha seus membros como propriedades somente leitura, e não campos, pois os campos impedem a vinculação de dados.

    • Não defina nenhuma AsyncCompletedEventArgsclasse derivada para métodos que não produzem resultados. Basta usar uma instância de AsyncCompletedEventArgs si mesmo.

      Nota

      É perfeitamente aceitável, quando viável e apropriado, reutilizar delegados e AsyncCompletedEventArgs tipos. Neste caso, a nomenclatura não será tão consistente com o nome do método, uma vez que um determinado delegado e AsyncCompletedEventArgs não será vinculado a um único método.

Opcionalmente Suporte Cancelamento

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

  • Sua classe, incluindo futuras adições antecipadas a ela, tem apenas uma operação assíncrona que suporta cancelamento?
  • As operações assíncronas que suportam cancelamento podem suportar várias operações pendentes? Ou seja, o método MethodNameAsync usa um userState parâmetro e permite várias invocações antes de aguardar a conclusão de alguma?

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

Visual Basic

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

C#

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

Se você definir o método, os CancelAsync(object userState) clientes devem ter cuidado ao escolher seus valores de estado para 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 como MethodNameAsyncCancel baseia-se em ser capaz de descobrir mais facilmente o método em um ambiente de design como o IntelliSense do Visual Studio. Isso agrupa os membros relacionados e os distingue de outros membros que não têm nada a ver com a funcionalidade assíncrona. Se você espera que possa haver operações assíncronas adicionais 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 vai atrapalhar a interface da classe com uma proliferação de métodos.

Esses métodos normalmente retornarão imediatamente, e a operação pode ou não ser cancelada. No manipulador de eventos para o evento MethodNameCompleted , o objeto MethodNameCompletedEventArgs contém um Cancelled campo, que os clientes podem usar para determinar se o cancelamento ocorreu.

Respeite a semântica de cancelamento descrita em Práticas recomendadas para implementar o padrão assíncrono baseado em eventos.

Opcionalmente, ofereça suporte à propriedade IsBusy

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

Respeite a IsBusy semântica descrita em Práticas recomendadas para implementar o padrão assíncrono baseado em eventos.

Opcionalmente, fornecer suporte para relatórios de progresso

É frequentemente desejável para uma operação assíncrona relatar o progresso durante sua operação. O padrão assíncrono baseado em eventos fornece uma diretriz para fazer isso.

  • Opcionalmente, defina um evento a ser gerado pela operação assíncrona e invocado no thread apropriado. O ProgressChangedEventArgs objeto carrega um indicador de progresso com valor inteiro que se espera que esteja entre 0 e 100.

  • Nomeie esse evento da seguinte maneira:

    • ProgressChanged se a classe tiver várias operações assíncronas (ou se se espera que cresça para incluir várias operações assíncronas em versões futuras);

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

    Essa escolha de nomenclatura é paralela à feita para o método de cancelamento, conforme descrito na seção Cancelamento de suporte opcional.

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

Observe que há apenas um ProgressChanged evento ou MethodNameProgressChanged para a classe, independentemente do número de métodos assíncronos suportados. Espera-se que os clientes usem o userState objeto que é passado para os métodos MethodNameAsync para distinguir entre atualizações de progresso em várias operações simultâneas.

Pode haver situações em que várias operações suportam o 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 ProgressChanged eventos. Nesse caso, use um padrão de nomenclatura de MethodNameProgressChanged para cada método MethodNameAsync.

Respeite a semântica de relatório de progresso descrita Práticas recomendadas para implementar o padrão assíncrono baseado em eventos.

Opcionalmente, fornecer suporte para retornar 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 dar suporte a esse cenário. Seguem-se alguns exemplos.

Classe de operação única

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

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

  • Acione esse evento MethodNameProgressChanged quando houver um resultado incremental a ser relatado.

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

Classe de operação múltipla 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 EventArgs estrutura funcionará para todos os resultados incrementais. Defina um ProgressChanged evento em vez de um evento MethodNameProgressChanged , pois ele se aplica a vários métodos assíncronos.

Classe de operação múltipla com resultados incrementais heterogêneos

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

  • Separe os relatórios de resultados incrementais dos relatórios de progresso.

  • Defina um evento MethodNameProgressChanged separado com apropriado EventArgs para cada método assíncrono para manipular os dados de resultado incremental desse método.

Invoque esse manipulador de eventos no thread apropriado, conforme descrito em Práticas recomendadas para implementar o padrão assíncrono baseado em eventos.

Manipulando parâmetros de saída e ref em métodos

Embora o uso de out e ref seja, em geral, desencorajado no .NET, aqui estão as regras a seguir quando eles estão presentes:

Dado um método síncrono MethodName:

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

  • refparâmetros 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, dado:

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 AsyncCompletedEventArgs classe teriam 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; };
}

Consulte também