Share via


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

O Padrão Assíncrono Baseado em Evento 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 e EventArgs concluídos

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 gerar o evento MethodNameCompleted . Este evento deve ser levantado na conclusão bem-sucedida, em um 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 Error à propriedade.

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

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

  • 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 e contextos.

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 .

  • Se sua classe definir a sobrecarga MethodNameAsync que usa um parâmetro de estado com valor de objeto ou ID de tarefa, certifique-se de controlar o tempo de vida da operação com essa ID de tarefa e certifique-se de fornecê-la de volta 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 gerar uma exceção quando sua classe explicitamente não pode lidar com essa situação, mas assumir que os desenvolvedores podem lidar com esses vários retornos de chamada 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 a pode sempre ser interpretada ProgressPercentage como uma percentagem. A percentagem não precisa de ser exata, mas deve representar uma percentagem. Se sua métrica de relatório de progresso deve ser algo diferente de uma porcentagem, derive uma classe da classe e saia ProgressChangedEventArgsProgressPercentage 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 e contextos.

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 voltar false. Os BackgroundWorker componentes e WebClient são exemplos de classes que expõem uma IsBusy propriedade.

Cancelamento

  • Suporte ao cancelamento, se possível. 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 CancelledAsyncCompletedEventArgs bandeira no objeto.

  • 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

  • Capture todas as exceções que ocorrem na operação assíncrona e defina o AsyncCompletedEventArgs.Error valor da propriedade 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 AsyncOperationarquivo . 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 Post métodos e OperationCompleted no AsyncOperation. AsyncOperation é responsável por organizar chamadas para os manipuladores de eventos do cliente para o thread ou contexto adequado.

Nota

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 ProgressChanged os eventos sejam levantados 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 AsyncOperationManager habilitar suas operações assíncronas, consulte Como implementar um componente que ofereça suporte ao padrão assíncrono baseado em evento.

Diretrizes

  • 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 a reutilização CompletedEventArgs de classes 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 lançar 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.

Consulte também