Partilhar via


Visão geral do padrão assíncrono baseado em evento

Os aplicativos que executam muitas tarefas simultaneamente, mas permanecem responsivos à interação do usuário, geralmente exigem um design que usa vários threads. O System.Threading namespace fornece todas as ferramentas necessárias para criar aplicativos multithreaded de alto desempenho, mas usar essas ferramentas efetivamente requer experiência significativa com engenharia de software multithreaded. Para aplicações multithreaded relativamente simples, o BackgroundWorker componente fornece uma solução simples. Para aplicativos assíncronos mais sofisticados, considere implementar uma classe que siga o padrão assíncrono baseado em eventos.

O padrão assíncrono baseado em eventos disponibiliza as vantagens dos aplicativos multithreaded enquanto oculta muitos dos problemas complexos inerentes ao design multithreaded. O uso de uma classe que ofereça suporte a esse padrão pode permitir:

  • Execute tarefas demoradas, como downloads e operações de banco de dados, "em segundo plano", sem interromper seu aplicativo.

  • Execute várias operações simultaneamente, recebendo notificações quando cada uma for concluída.

  • Aguarde até que os recursos fiquem disponíveis sem parar ("bloquear") seu aplicativo.

  • Comunique-se com operações assíncronas pendentes usando o modelo familiar de eventos e delegados. Para obter mais informações sobre como usar manipuladores de eventos e delegados, consulte Eventos.

Uma classe que ofereça suporte ao padrão assíncrono baseado em evento terá um ou mais métodos chamados MethodNameAsync. Esses métodos podem espelhar versões síncronas, que executam a mesma operação no thread atual. A classe também pode ter um evento MethodNameCompleted e pode ter um método MethodNameAsyncCancel (ou simplesmente CancelAsync).

PictureBox é um componente típico que suporta o padrão assíncrono baseado em eventos. Você pode baixar uma imagem de forma síncrona chamando seu Load método, mas se a imagem for grande ou se a conexão de rede estiver lenta, seu aplicativo deixará de responder até que a operação de download seja concluída e a chamada para Load retorne.

Se você quiser que seu aplicativo continue em execução enquanto a imagem está carregando, você pode chamar o LoadAsync método e manipular o LoadCompleted evento, assim como você lidaria com qualquer outro evento. Quando você chamar o LoadAsync método, seu aplicativo continuará a ser executado enquanto o download prossegue em um thread separado ("em segundo plano"). O manipulador de eventos será chamado quando a operação de carregamento de imagem for concluída e o manipulador de eventos poderá examinar o AsyncCompletedEventArgs parâmetro para determinar se o download foi concluído com êxito.

O padrão assíncrono baseado em evento requer que uma operação assíncrona possa ser cancelada, e o PictureBox controle suporta esse requisito com seu CancelAsync método. A chamada CancelAsync envia uma solicitação para interromper o download pendente e, quando a tarefa é cancelada, o LoadCompleted evento é gerado.

Atenção

É possível que o download termine assim que a CancelAsync solicitação é feita, portanto Cancelled , pode não refletir a solicitação de cancelamento. Isso é chamado de condição de corrida e é um problema comum na programação multithreaded. Para obter mais informações sobre problemas na programação multithreaded, consulte Managed Threading Best Practices.

Características do padrão assíncrono baseado em evento

O padrão assíncrono baseado em eventos pode assumir várias formas, dependendo da complexidade das operações suportadas por uma classe específica. As classes mais simples podem ter um único método MethodNameAsync e um evento MethodNameCompleted correspondente. Classes mais complexas podem ter vários métodos MethodNameAsync, cada um com um evento MethodNameCompleted correspondente, bem como versões síncronas desses métodos. Opcionalmente, as classes podem oferecer suporte a cancelamento, relatórios de progresso e resultados incrementais para cada método assíncrono.

Um método assíncrono também pode suportar várias chamadas pendentes (várias invocações simultâneas), permitindo que seu código o chame a qualquer número de vezes antes de concluir outras operações pendentes. Lidar corretamente com essa situação pode exigir que seu aplicativo acompanhe a conclusão de cada operação.

Exemplos do padrão assíncrono baseado em evento

Os SoundPlayer componentes e PictureBox representam implementações simples do padrão assíncrono baseado em eventos. Os WebClient componentes e BackgroundWorker representam implementações mais complexas do padrão assíncrono baseado em eventos.

Abaixo está um exemplo de declaração de classe que está em conformidade com o padrão:

Public Class AsyncExample  
    ' Synchronous methods.  
    Public Function Method1(ByVal param As String) As Integer
    Public Sub Method2(ByVal param As Double)
  
    ' Asynchronous methods.  
    Overloads Public Sub Method1Async(ByVal param As String)
    Overloads Public Sub Method1Async(ByVal param As String, ByVal userState As Object)
    Public Event Method1Completed As Method1CompletedEventHandler  
  
    Overloads Public Sub Method2Async(ByVal param As Double)
    Overloads Public Sub Method2Async(ByVal param As Double, ByVal userState As Object)
    Public Event Method2Completed As Method2CompletedEventHandler  
  
    Public Sub CancelAsync(ByVal userState As Object)
  
    Public ReadOnly Property IsBusy () As Boolean  
  
    ' Class implementation not shown.  
End Class  
public class AsyncExample  
{  
    // Synchronous methods.  
    public int Method1(string param);  
    public void Method2(double param);  
  
    // Asynchronous methods.  
    public void Method1Async(string param);  
    public void Method1Async(string param, object userState);  
    public event Method1CompletedEventHandler Method1Completed;  
  
    public void Method2Async(double param);  
    public void Method2Async(double param, object userState);  
    public event Method2CompletedEventHandler Method2Completed;  
  
    public void CancelAsync(object userState);  
  
    public bool IsBusy { get; }  
  
    // Class implementation not shown.  
}  

A classe fictícia AsyncExample tem dois métodos, ambos suportando invocações síncronas e assíncronas. As sobrecargas síncronas se comportam como qualquer chamada de método e executam a operação no thread de chamada; Se a operação for demorada, pode haver um atraso percetível antes que a chamada retorne. As sobrecargas assíncronas iniciarão a operação em outro thread e, em seguida, retornarão imediatamente, permitindo que o thread de chamada continue enquanto a operação é executada "em segundo plano".

Sobrecargas de método assíncronas

Há potencialmente duas sobrecargas para as operações assíncronas: invocação única e invocação múltipla. Você pode distinguir esses dois formulários por suas assinaturas de método: o formulário de invocação múltipla tem um parâmetro extra chamado userState. Este formulário possibilita que seu código chame Method1Async(string param, object userState) várias vezes sem esperar que nenhuma operação assíncrona pendente seja concluída. Se, por outro lado, você tentar chamar Method1Async(string param) antes que uma invocação anterior tenha sido concluída, o método gerará um InvalidOperationExceptionarquivo .

O userState parâmetro para as sobrecargas de invocação múltipla permite distinguir entre operações assíncronas. Você fornece um valor exclusivo (por exemplo, um GUID ou código hash) para cada chamada para Method1Async(string param, object userState), e quando cada operação é concluída, seu manipulador de eventos pode determinar qual instância da operação gerou o evento de conclusão.

Rastreando operações pendentes

Se você usar as sobrecargas de invocação múltipla, seu código precisará controlar os userState objetos (IDs de tarefa) para tarefas pendentes. Para cada chamada para Method1Async(string param, object userState)o , você normalmente gerará um objeto novo e exclusivo userState e o adicionará a uma coleção. Quando a tarefa correspondente a esse userState objeto gerar o evento de conclusão, a implementação do método de conclusão será examinada AsyncCompletedEventArgs.UserState e removida da coleção. Usado dessa forma, o userState parâmetro assume a função de um ID de tarefa.

Nota

Você deve ter cuidado para fornecer um valor exclusivo para userState suas chamadas para sobrecargas de invocação múltipla. IDs de tarefa não exclusivas farão com que a classe assíncrona lance um ArgumentExceptionarquivo .

Cancelando operações pendentes

É importante poder cancelar operações assíncronas a qualquer momento antes de sua conclusão. As classes que implementam o padrão assíncrono baseado em evento terão um CancelAsync método (se houver apenas um método assíncrono) ou um método MethodNameAsyncCancel (se houver vários métodos assíncronos).

Os métodos que permitem várias invocações usam um userState parâmetro, que pode ser usado para controlar o tempo de vida de cada tarefa. CancelAsync usa um userState parâmetro, que permite cancelar tarefas pendentes específicas.

Os métodos que suportam apenas uma única operação pendente de cada vez, como Method1Async(string param), não são canceláveis.

Recebendo atualizações de progresso e resultados incrementais

Uma classe que adere ao padrão assíncrono baseado em eventos pode, opcionalmente, fornecer um evento para acompanhar o progresso e os resultados incrementais. Isso normalmente será nomeado ProgressChanged ou MethodNameProgressChanged, e seu manipulador de eventos correspondente usará um ProgressChangedEventArgs parâmetro.

O manipulador de eventos para o evento pode examinar a ProgressChangedEventArgs.ProgressPercentage propriedade para determinar qual porcentagem de uma tarefa assíncrona ProgressChanged foi concluída. Essa propriedade varia de 0 a 100 e pode ser usada para atualizar a Value propriedade de um ProgressBararquivo . Se várias operações assíncronas estiverem pendentes, você poderá usar a ProgressChangedEventArgs.UserState propriedade para distinguir qual operação está relatando o progresso.

Algumas classes podem relatar resultados incrementais à medida que as operações assíncronas prosseguem. Esses resultados serão armazenados em uma classe que deriva de ProgressChangedEventArgs e eles aparecerão como propriedades na classe derivada. Você pode acessar esses resultados no manipulador de eventos para o ProgressChanged evento, assim como você acessaria a ProgressPercentage propriedade. Se várias operações assíncronas estiverem pendentes, você poderá usar a UserState propriedade para distinguir qual operação está relatando resultados incrementais.

Consulte também