Share via


Práticas recomendadas para a implementação do padrão assíncrono baseado em evento

O padrão assíncrono baseado em eventos proporciona uma maneira eficiente de expor o comportamento assíncrono nas classes, com evento familiar e semântica de representante. Para implementá-lo, você deve seguir alguns requisitos de comportamento específicos. As seções a seguir descrevem os requisitos e as diretrizes a serem considerados ao implementar uma classe que segue o padrão assíncrono baseado em eventos.

Para obter uma visão geral, veja Implementação do padrão assíncrono baseado em evento.

Garantias comportamentais necessárias

Se implementar o padrão assíncrono baseado em eventos, você deve fornecer diversas garantias para assegurar que a classe se comportará corretamente e que os clientes da classe podem confiar nesse comportamento.

Completion

Sempre invoque o manipulador de eventos MethodNameCompleted quando houver uma conclusão com êxito, um erro ou um cancelamento. Os aplicativos nunca devem permanecer inativos sem que a conclusão ocorra. Uma exceção a essa regra é se a própria operação assíncrona for projetada para nunca ser concluída.

Evento e argumentos de eventos concluídos

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

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

  • Defina uma classe EventArgs e o delegado de acompanhamento para o evento MethodNameCompleted que é derivado da classe AsyncCompletedEventArgs. O nome padrão da classe deve seguir a forma MethodNameCompletedEventArgs.

  • Verifique se a classe EventArgs é específica aos valores retornados do método MethodName. Ao usar a classe EventArgs, você nunca deve solicitar que os desenvolvedores convertam o resultado.

    O exemplo de código a seguir mostra as implementações correta e incorreta desse requisito, 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 classe EventArgs para os métodos de retorno que retornam void. Em vez disso, use uma instância da classe AsyncCompletedEventArgs.

  • O evento MethodNameCompleted sempre deve ser usado. Esse evento deve ser usado quando a conclusão for realizada com êxito, em caso de erro ou em caso de cancelamento. Os aplicativos nunca devem permanecer inativos sem que a conclusão ocorra.

  • Você deve capturar todas as exceções que ocorrerem na operação assíncrona e atribuir a exceção de captura à propriedade Error.

  • Caso ocorra um erro ao concluir a tarefa, os resultados não devem estar acessíveis. Quando a propriedade Error não for null, verifique se o acesso a qualquer propriedade na estrutura EventArgs gera uma exceção. Use o método RaiseExceptionIfNecessary para fazer essa verificação.

  • Defina o tempo limite como um erro. Em caso de evento de tempo limite, gere o evento MethodNameCompleted e atribua um TimeoutException à propriedade Error.

  • Se sua classe permite usar diversas invocações ao mesmo tempo, garanta que o evento MethodNameCompleted tenha o objeto userSuppliedState apropriado.

  • Verifique se o evento MethodNameCompleted é gerado no thread certo e no momento certo do ciclo de vida do aplicativo. Para obter mais informações, consulte a seção Threads e contextos.

Operações em execução simultânea

  • Se sua classe permite fazer diversas invocações ao mesmo tempo, permita que o desenvolvedor acompanhe cada invocação separadamente. Para isso, defina a sobrecarga de MethodNameAsync que usa um parâmetro de estado com valor de objeto, ou ID de tarefa, chamado userSuppliedState. Esse parâmetro sempre deve ser o último presente na assinatura do método MethodNameAsync.

  • Se sua classe define a sobrecarga de MethodNameAsync que usa um parâmetro de estado com valor de objeto, ou ID de tarefa, acompanhe o tempo de vida da operação com essa ID de tarefa e informe o manipulador de conclusão. Há algumas classes auxiliares disponíveis para ajudar. Para saber mais sobre o gerenciamento de simultaneidade, veja Como implementar um componente compatível com o Padrão Assíncrono baseado em Evento.

  • Se sua classe define o método MethodNameAsync sem o parâmetro de estado e não é compatível com diversas invocações ao mesmo tempo, verifique se as tentativas de invocar MethodNameAsync antes da conclusão da invocação anterior de MethodNameAsync gera um InvalidOperationException.

  • Em geral, não gere a exceção se o método MethodNameAsync sem o parâmetro userSuppliedState for invocado diversas vezes para que haja diversas operações pendentes. Você pode gerar uma exceção quando a classe não puder explicitamente lidar com essa situação, e você deduzir que os desenvolvedores podem lidar com esses diversos retornos de chamada indistinguíveis.

Acessando os resultados

Relatório de progresso

  • Permita o uso de relatórios de progresso, se possível. Isso permite que os desenvolvedores melhorem a experiência dos usuários do aplicativo quando eles usam sua classe.

  • Se você implementar um evento ProgressChanged ou MethodNameProgressChanged, verifique se não há eventos desse tipo para operações assíncronas específicas após esse evento MethodNameCompleted da operação ser gerado.

  • Se o ProgressChangedEventArgs padrão estiver sendo preenchido, verifique se ProgressPercentage sempre pode ser identificado como uma porcentagem. A porcentagem não precisa ser precisa, mas deve representar uma porcentagem. Se a métrica do relatório de progresso não tiver que ser uma porcentagem, derive uma classe da classe ProgressChangedEventArgs e deixe ProgressPercentage como 0. Evite usar outras métricas além de porcentagem nos relatórios.

  • Verifique se o evento ProgressChanged é gerado no thread certo e no momento certo do ciclo de vida do aplicativo. Para obter mais informações, consulte a seção Threads e contextos.

Implementação IsBusy

  • Não exponha a propriedade IsBusy se a classe for compatível com diversas invocações simultâneas. Por exemplo, os proxies de serviço Web XML não expõem a propriedade IsBusy porque permitem usar diversas invocações simultâneas de métodos assíncronos.

  • A propriedade IsBusy deve retornar true depois que o método MethodNameAsync for chamado e antes da geração do evento MethodNameCompleted. Caso contrário, ela deve retornar false. Os componentes BackgroundWorker e WebClient são exemplos de classes que expõem uma propriedade IsBusy.

Cancelamento

  • Permita o cancelamento, se possível. Isso permite que os desenvolvedores melhorem a experiência dos usuários do aplicativo quando eles usam sua classe.

  • Em caso de cancelamento, defina o sinalizador Cancelled no objeto AsyncCompletedEventArgs.

  • Verifique se todas as tentativas de acessar o resultado geram um InvalidOperationException, indicando que a operação foi cancelada. Use o método AsyncCompletedEventArgs.RaiseExceptionIfNecessary para fazer essa verificação.

  • Verifique se as chamadas de um método de cancelamento sempre retornam com êxito e nunca geram uma exceção. Em geral, o cliente não é notificado se a operação pode mesmo ser cancelada a qualquer momento, nem se o cancelamento emitido foi realizado. No entanto, o aplicativo sempre recebe uma notificação quando o cancelamento é realizado. Isso acontece porque o aplicativo participa do status de conclusão.

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

Erros e exceções

  • Capture todas as exceções que ocorrerem na operação assíncrona e defina o valor da propriedade AsyncCompletedEventArgs.Error para essa exceção.

Threads e contextos

Para que sua classe opere corretamente, é essencial que os indicadores de eventos do cliente sejam invocados no thread ou contexto certo de acordo com o modelo do aplicativo, incluindo ASP.NET e aplicativos Windows Forms. Duas importantes classes auxiliares são fornecidas para garantir que a 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 AsyncOperation retornado para acompanhar o tempo de vida da tarefa assíncrona.

Para criar relatórios de progresso, resultados incrementais e conclusão para o cliente, chame os métodos Post e OperationCompleted no AsyncOperation. AsyncOperation é responsável por fazer marshalling de chamadas para os identificadores de evento de cliente para o thread ou contexto adequado.

Observação

Você pode driblar essas regras se quiser explicitamente ir contra a política de modelo de aplicativos, e ainda aproveitar outras vantagens do padrão assíncrono baseado em eventos. Por exemplo, uma classe pode operar no Windows Forms para ter threads livres. Você pode criar uma classe de thread livre, contanto que os desenvolvedores conheçam as restrições inerentes. Os aplicativos de console não sincronizam a execução das chamadas de Post. Isso pode fazer com que os eventos ProgressChanged sejam gerados fora de ordem. Se quiser que as chamadas Post sejam executadas em série, implemente e instale uma classe System.Threading.SynchronizationContext.

Para saber mais sobre como usar AsyncOperation e AsyncOperationManager para habilitar as operações assíncronas, veja Como implementar um componente compatível com o Padrão Assíncrono baseado em Evento.

Diretrizes

  • De modo ideal, cada invocação de método deve ser independente das demais. Você deve evitar o agrupamento de invocações e recursos compartilhados. Se for necessário compartilhar os recursos entre as invocações, será necessário fornecer um mecanismo de sincronização adequado na sua implementação.

  • Não recomendamos o uso de designs que requerem que o cliente implemente a sincronização. Por exemplo, você pode ter um método assíncrono que recebe um objeto estático global como parâmetro, e diversas invocações simultâneas desse método podem resultar em dados corrompidos ou deadlocks.

  • Se você implementar um método com sobrecarga de diversas invocações (userState na assinatura), sua classe precisará gerenciar uma coleção de estados dos usuários ou IDs de tarefas e suas respectivas operações pendentes. Essa coleção deve ser protegida com as regiões lock porque as diversas invocações adicionam e removem userState objetos da coleção.

  • Considere reutilizar as classes CompletedEventArgs quando possível e adequado. Nesse caso, a nomenclatura não é consistente com o nome do método porque um determinado representante e o tipo EventArgs não sã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 que deriva de Component, não implemente nem instale sua própria classe SynchronizationContext. Os modelos do aplicativo, e não os componentes, controlam o SynchronizationContext usado.

  • Ao usar multithreading de qualquer tipo, você pode enfrentar bugs muito sérios e complexos. Veja as Práticas recomendadas de threading gerenciado antes de implementar qualquer solução que use multithreading.

Confira também