Partilhar via


Práticas recomendadas para implementar o padrão assíncrono baseado em eventos

O Padrão Assíncrono Baseado em Eventos fornece uma maneira eficaz de expor o comportamento assíncrono em classes, com semântica familiar de eventos e delegados. Para implementar o padrão assíncrono baseado em eventos, você precisa seguir alguns requisitos comportamentais específicos. As seções a seguir descrevem os requisitos e diretrizes que você deve considerar ao implementar uma classe que segue o padrão assíncrono baseado em eventos.

Para obter uma visão geral, consulte Implementando o padrão assíncrono baseado em eventos.

Garantias comportamentais exigidas

Se você implementar o padrão assíncrono baseado em eventos, deverá fornecer várias garantias para garantir que sua classe se comporte corretamente e que os clientes de sua classe possam confiar nesse comportamento.

Conclusão

Sempre invoque o manipulador de eventos MethodNameCompleted quando tiver uma conclusão bem-sucedida, um erro ou um cancelamento. Os aplicativos nunca devem encontrar uma situação em que permaneçam ociosos e a conclusão nunca ocorra. Uma exceção a essa regra é se a própria operação assíncrona for projetada para que nunca seja concluída.

Evento concluído e EventArgs

Para cada método MethodNameAsync separado, aplique os seguintes requisitos de design:

  • Defina um evento MethodNameCompleted na mesma classe que o método.

  • Defina uma classe e um EventArgs delegado que a acompanha para o evento MethodNameCompleted que deriva da AsyncCompletedEventArgs classe. O nome da classe padrão deve ser do formato MethodNameCompletedEventArgs.

  • Certifique-se de que a EventArgs classe é específica para os valores de retorno do método MethodName . Quando você usa a EventArgs classe, nunca deve exigir que os desenvolvedores convertam o resultado.

    O exemplo de código a seguir mostra a implementação boa e ruim desse requisito de design, respectivamente.

// Good design
private void Form1_MethodNameCompleted(object sender, xxxCompletedEventArgs e)
{
    DemoType result = e.Result;
}

// Bad design
private void Form1_MethodNameCompleted(object sender, MethodNameCompletedEventArgs e)
{
    DemoType result = (DemoType)(e.Result);
}
  • Não defina uma EventArgs classe para retornar métodos que retornam void. Em vez disso, use uma instância da AsyncCompletedEventArgs classe.

  • Certifique-se de sempre acionar o evento MethodNameCompleted. Este evento deve ser acionado na conclusão bem-sucedida, no caso de erro ou no cancelamento. Os aplicativos nunca devem encontrar uma situação em que permaneçam ociosos e a conclusão nunca ocorra.

  • Certifique-se de capturar todas as exceções que ocorrem na operação assíncrona e atribuir a exceção capturada à propriedade Error.

  • Se houve um erro ao concluir a tarefa, os resultados não devem estar acessíveis. Quando a propriedade Error não for null, certifique-se de que aceder a qualquer propriedade na estrutura EventArgs gera uma exceção. Use o RaiseExceptionIfNecessary método para executar essa verificação.

  • Modele um timeout como um erro. Quando ocorrer um tempo limite, dispare o evento MethodNameCompleted e atribua TimeoutException à propriedade Error.

  • Se sua classe oferecer suporte a várias invocações simultâneas, verifique se o evento MethodNameCompleted contém o objeto apropriado userSuppliedState .

  • Certifique-se de que o evento MethodNameCompleted seja gerado no thread apropriado e no momento apropriado no ciclo de vida do aplicativo. Para obter mais informações, consulte a seção Threading and Contexts.

Execução simultânea de operações

  • Se sua classe oferecer suporte a várias invocações simultâneas, permita que o desenvolvedor acompanhe cada invocação separadamente definindo a sobrecarga MethodNameAsync que usa um parâmetro de estado com valor de objeto, ou ID de tarefa, chamado userSuppliedState. Este parâmetro deve ser sempre o último parâmetro na assinatura do método MethodNameAsync .

  • Caso a sua classe defina a sobrecarga MethodNameAsync que usa um parâmetro de estado com valor de objeto ou ID de tarefa, assegure-se de monitorar o tempo de vida da operação com essa ID de tarefa e assegure-se de retorná-la ao manipulador de conclusão. Há aulas auxiliares disponíveis para ajudar. Para obter mais informações sobre gerenciamento de simultaneidade, consulte Como implementar um componente que ofereça suporte ao padrão assíncrono baseado em eventos.

  • Se sua classe definir o método MethodNameAsync sem o parâmetro state e não oferecer suporte a várias invocações simultâneas, certifique-se de que qualquer tentativa de invocar MethodNameAsync antes que a invocação MethodNameAsync anterior tenha sido concluída gere um InvalidOperationExceptionarquivo .

  • Em geral, não gere uma exceção se o método MethodNameAsync sem o userSuppliedState parâmetro for invocado várias vezes para que haja várias operações pendentes. Você pode lançar uma exceção quando a sua classe não puder lidar explicitamente com essa situação, mas presuma que os desenvolvedores podem lidar com múltiplos callbacks indistinguíveis.

Acesso aos resultados

Relatório de progresso

  • Apoie a elaboração de relatórios de progresso, se possível. Isso permite que os desenvolvedores forneçam uma melhor experiência de usuário do aplicativo quando usarem sua classe.

  • Se você implementar um evento ProgressChanged ou MethodNameProgressChanged , certifique-se de que não haja tais eventos gerados para uma operação assíncrona específica depois que o evento MethodNameCompleted dessa operação tiver sido gerado.

  • Se a norma ProgressChangedEventArgs estiver a ser preenchida, certifique-se de que ProgressPercentage pode sempre ser interpretado como uma percentagem. A percentagem não precisa de ser exata, mas deve representar uma percentagem. Se a sua métrica de relatório de progresso tiver que ser algo diferente de uma porcentagem, derive uma classe da classe ProgressChangedEventArgs e deixe ProgressPercentage em 0. Evite usar uma métrica de relatório diferente de uma porcentagem.

  • Certifique-se de que o ProgressChanged evento seja gerado no thread apropriado e no momento apropriado no ciclo de vida do aplicativo. Para obter mais informações, consulte a seção Threading and Contexts.

Implementação do IsBusy

  • Não exponha uma IsBusy propriedade se sua classe oferecer suporte a várias invocações simultâneas. Por exemplo, proxies de XML Web Services não expõem uma IsBusy propriedade porque oferecem suporte a várias invocações simultâneas de métodos assíncronos.

  • A IsBusy propriedade deve retornar true após o método MethodNameAsync ter sido chamado e antes que o evento MethodNameCompleted tenha sido gerado. Caso contrário, deve retornar false. Os BackgroundWorker componentes e WebClient são exemplos de classes que expõem uma IsBusy propriedade.

Cancelamento

  • Se possível, suporte ao cancelamento. Isso permite que os desenvolvedores forneçam uma melhor experiência de usuário do aplicativo quando usarem sua classe.

  • Em caso de cancelamento, defina a bandeira Cancelled no objeto AsyncCompletedEventArgs.

  • Certifique-se de que qualquer tentativa de acessar o resultado gere uma InvalidOperationException declaração de que a operação foi cancelada. Use o AsyncCompletedEventArgs.RaiseExceptionIfNecessary método para executar essa verificação.

  • Certifique-se de que as chamadas para um método de cancelamento sempre retornem com êxito e nunca levantem uma exceção. Em geral, um cliente não é notificado sobre se uma operação é realmente cancelável em um determinado momento, e não é notificado sobre se um cancelamento emitido anteriormente foi bem-sucedido. No entanto, o pedido será sempre notificado quando um cancelamento for bem-sucedido, porque o pedido participa no estado de conclusão.

  • Levante o evento MethodNameCompleted quando a operação for cancelada.

Erros e Exceções

  • Intercepte quaisquer exceções que ocorram na operação assíncrona e defina o valor da propriedade AsyncCompletedEventArgs.Error como essa exceção.

Threading e contextos

Para a operação correta de sua classe, é fundamental que os manipuladores de eventos do cliente sejam invocados no thread ou contexto adequado para um determinado modelo de aplicativo, incluindo aplicativos ASP.NET e Windows Forms. Duas classes auxiliares importantes são fornecidas para garantir que sua classe assíncrona se comporte corretamente em qualquer modelo de aplicativo: AsyncOperation e AsyncOperationManager.

AsyncOperationManager fornece um método, CreateOperation, que retorna um AsyncOperation. Seu método MethodNameAsync chama CreateOperation e sua classe usa o retornado AsyncOperation para controlar o tempo de vida da tarefa assíncrona.

Para relatar o progresso, os resultados incrementais e a conclusão para o cliente, chame os métodos Post e OperationCompleted no AsyncOperation. AsyncOperation é responsável por organizar chamadas para os manipuladores de eventos do cliente para o thread ou contexto adequado.

Observação

Você pode contornar essas regras se quiser explicitamente ir contra a política do modelo de aplicativo, mas ainda se beneficiar das outras vantagens de usar o padrão assíncrono baseado em eventos. Por exemplo, você pode desejar que uma classe que opera no Windows Forms seja encadeada livremente. Você pode criar uma classe threaded livre, desde que os desenvolvedores entendam as restrições implícitas. Os aplicativos de console não sincronizam a execução de Post chamadas. Isso pode fazer com que os eventos ProgressChanged ocorram fora de ordem. Se você deseja ter a execução serializada de Post chamadas, implemente e instale uma System.Threading.SynchronizationContext classe.

Para obter mais informações sobre como usar AsyncOperation e habilitar suas operações assíncronas, consulte AsyncOperationManager.

Orientações

  • Idealmente, cada invocação de método deve ser independente de outros. Você deve evitar associar invocações com recursos compartilhados. Se os recursos forem compartilhados entre invocações, você precisará fornecer um mecanismo de sincronização adequado em sua implementação.

  • Os designs que exigem que o cliente implemente a sincronização são desencorajados. Por exemplo, você pode ter um método assíncrono que recebe um objeto estático global como um parâmetro; Várias invocações simultâneas de tal método podem resultar em corrupção de dados ou bloqueios.

  • Se você implementar um método com a sobrecarga de invocação múltipla (userState na assinatura), sua classe precisará gerenciar uma coleção de estados de usuário ou IDs de tarefa e suas operações pendentes correspondentes. Essa coleção deve ser protegida com lock regiões, porque as várias invocações adicionam e removem userState objetos na coleção.

  • Considere reutilizar as classes CompletedEventArgs sempre que viável e apropriado. Nesse caso, a nomenclatura não é consistente com o nome do método, porque um determinado delegado e EventArgs tipo não estão vinculados a um único método. No entanto, forçar os desenvolvedores a converter o valor recuperado de uma propriedade no EventArgs nunca é aceitável.

  • Se você estiver criando uma classe derivada do Component, não implemente e instale sua própria SynchronizationContext classe. Os modelos de aplicação, não os componentes, controlam o SynchronizationContext que é usado.

  • Quando você usa multithreading de qualquer tipo, você potencialmente se expõe a bugs muito sérios e complexos. Antes de implementar qualquer solução que use multithreading, consulte Práticas recomendadas de threading gerenciado.

Ver também