Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Um evento é uma ação à qual você pode responder ou "manipular" em código. Os eventos geralmente são gerados por uma ação do usuário, como clicar com o mouse ou pressionar uma tecla, mas também podem ser gerados pelo código do programa ou pelo sistema.
Aplicações orientadas por eventos executam código em resposta a um evento. Cada formulário e controle expõe um conjunto predefinido de eventos aos quais você pode responder. Se um desses eventos for gerado e houver um manipulador de eventos associado, o manipulador será invocado e o código será executado.
Os tipos de eventos gerados por um objeto variam, mas muitos tipos são comuns à maioria dos controles. Por exemplo, a maioria dos objetos tem um Click evento que é gerado quando um usuário clica nele.
Observação
Muitos eventos ocorrem com outros eventos. Por exemplo, no decorrer do evento DoubleClick ocorrendo, os eventos MouseDown, MouseUpe Click ocorrem.
Para obter informações gerais sobre como gerar e consumir um evento, consulte Manipulando e gerando eventos no .NET.
Os delegados e o seu papel
Delegados são classes comumente usadas no .NET para criar mecanismos de manipulação de eventos. Os delegados são aproximadamente equivalentes a ponteiros de função, frequentemente usados no Visual C++ e em outras linguagens orientadas a objetos. Ao contrário dos ponteiros de função, no entanto, os delegados são orientados a objetos, seguros para tipos e protegidos. Além disso, quando um ponteiro de função contém apenas uma referência a uma função específica, um delegado consiste em uma referência a um objeto e referências a um ou mais métodos dentro do objeto.
Esse modelo de evento usa delegados para vincular eventos aos métodos usados para manipulá-los. O delegado permite que outras classes se registrem para notificação de eventos especificando um método manipulador. Quando o evento ocorre, o delegado chama o método vinculado. Para obter mais informações sobre como definir delegados, consulte Manipulando e gerando eventos.
Os delegados podem ser vinculados a um único método ou a vários métodos, conhecidos como multicasting. Ao criar um representante para um evento, normalmente você cria um evento multicast. Uma exceção rara pode ser um evento que resulta em um procedimento específico (como exibir uma caixa de diálogo) que não se repetiria logicamente várias vezes por evento. Para obter informações sobre como criar um delegado multicast, consulte Como combinar delegados (Delegados Multicast).
Um delegado multicast mantém uma lista de invocação dos métodos associados a ele. O delegado de multicast suporta um método Combine para adicionar um método à lista de invocação e um método Remove para removê-lo.
Quando um aplicativo registra um evento, o controle gera o evento invocando o delegado para esse evento. O delegado, por sua vez, chama o método vinculado. No caso mais comum (um delegado de múltipla difusão), o delegado chama cada método associado na lista de invocação por sua vez, o que fornece uma notificação um para vários. Essa estratégia significa que o controle não precisa manter uma lista de objetos de destino para notificação de eventos — o delegado lida com todos os registros e notificações.
Os delegados também permitem que vários eventos sejam associados ao mesmo método, permitindo uma notificação de muitos para um. Por exemplo, um evento button-click e um evento menu-command–click podem invocar o mesmo delegado, que então chama um único método para manipular esses eventos separados da mesma maneira.
O mecanismo de vinculação usado com delegados é dinâmico: um delegado pode ser vinculado em tempo de execução a qualquer método cuja assinatura corresponda à do manipulador de eventos. Com esse recurso, você pode configurar ou alterar o método acoplado dependendo de uma condição e anexar dinamicamente um manipulador de eventos a um controle.
Eventos no Windows Forms
Os eventos no Windows Forms são declarados com o delegado EventHandler<TEventArgs> para métodos manipuladores. Cada manipulador de eventos fornece dois parâmetros que permitem manipular o evento corretamente. O exemplo a seguir mostra um manipulador de eventos para o evento Button do controle Click.
Private Sub button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles button1.Click
End Sub
private void button1_Click(object sender, System.EventArgs e)
{
}
O primeiro parâmetro,sender, fornece uma referência ao objeto que gerou o evento. O segundo parâmetro, e, passa um objeto específico para o evento que está sendo manipulado. Ao fazer referência às propriedades do objeto (e, às vezes, aos seus métodos), pode-se obter informações como a localização do rato para eventos de rato ou dados que estão a ser transferidos em eventos de arraste e largue.
Normalmente, cada evento produz um manipulador de eventos com um tipo de objeto de evento diferente para o segundo parâmetro. Alguns manipuladores de eventos, como aqueles para os eventos MouseDown e MouseUp, têm o mesmo tipo de objeto para seu segundo parâmetro. Para esses tipos de eventos, você pode usar o mesmo manipulador de eventos para manipular ambos os eventos.
Você também pode usar o mesmo manipulador de eventos para manipular o mesmo evento para controles diferentes. Por exemplo, se tiver um grupo de RadioButton controlos num formulário, pode criar um único manipulador para o evento de Click de cada RadioButton. Para obter mais informações, consulte Como manipular um evento de controle.
Manipuladores de eventos assíncronos
Os aplicativos modernos geralmente precisam executar operações assíncronas em resposta a ações do usuário, como baixar dados de um serviço Web ou acessar arquivos. Os manipuladores de eventos do Windows Forms podem ser declarados como async métodos para dar suporte a esses cenários, mas há considerações importantes para evitar armadilhas comuns.
Padrão básico do manipulador de eventos assíncrono
Os manipuladores de eventos podem ser declarados com o async modificador (Async no Visual Basic) e usar await (Await no Visual Basic) para operações assíncronas. Como os manipuladores de eventos devem retornar void (ou ser declarados como um Sub no Visual Basic), eles são um dos raros usos aceitáveis de async void (ou Async Sub no Visual Basic):
private async void downloadButton_Click(object sender, EventArgs e)
{
downloadButton.Enabled = false;
statusLabel.Text = "Downloading...";
try
{
using var httpClient = new HttpClient();
string content = await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md");
// Update UI with the result
loggingTextBox.Text = content;
statusLabel.Text = "Download complete";
}
catch (Exception ex)
{
statusLabel.Text = $"Error: {ex.Message}";
}
finally
{
downloadButton.Enabled = true;
}
}
Private Async Sub downloadButton_Click(sender As Object, e As EventArgs) Handles downloadButton.Click
downloadButton.Enabled = False
statusLabel.Text = "Downloading..."
Try
Using httpClient As New HttpClient()
Dim content As String = Await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md")
' Update UI with the result
loggingTextBox.Text = content
statusLabel.Text = "Download complete"
End Using
Catch ex As Exception
statusLabel.Text = $"Error: {ex.Message}"
Finally
downloadButton.Enabled = True
End Try
End Sub
Importante
Embora async void seja desencorajado, é necessário para manipuladores de eventos (e código semelhante ao manipulador de eventos, como Control.OnClick) já que eles não podem retornar Task. Sempre encapsular operações aguardadas em try-catch blocos para lidar com exceções corretamente, como mostrado no exemplo anterior.
Armadilhas e impasses comuns
Advertência
Nunca use o bloqueio de chamadas como .Wait(), .Resultou .GetAwaiter().GetResult() em manipuladores de eventos ou qualquer código de interface do usuário. Esses padrões podem causar impasses.
O código a seguir demonstra um antipadrão comum que causa deadlocks:
// DON'T DO THIS - causes deadlocks
private void badButton_Click(object sender, EventArgs e)
{
try
{
// This blocks the UI thread and causes a deadlock
string content = DownloadPageContentAsync().GetAwaiter().GetResult();
loggingTextBox.Text = content;
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}");
}
}
private async Task<string> DownloadPageContentAsync()
{
using var httpClient = new HttpClient();
await Task.Delay(2000); // Simulate delay
return await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md");
}
' DON'T DO THIS - causes deadlocks
Private Sub badButton_Click(sender As Object, e As EventArgs) Handles badButton.Click
Try
' This blocks the UI thread and causes a deadlock
Dim content As String = DownloadPageContentAsync().GetAwaiter().GetResult()
loggingTextBox.Text = content
Catch ex As Exception
MessageBox.Show($"Error: {ex.Message}")
End Try
End Sub
Private Async Function DownloadPageContentAsync() As Task(Of String)
Using httpClient As New HttpClient()
Return Await httpClient.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md")
End Using
End Function
Isso causa um impasse pelos seguintes motivos:
- O thread da interface do usuário chama o método assíncrono e bloqueia a espera pelo resultado.
- O método assíncrono captura o thread da interface de utilizador
SynchronizationContext. - Quando a operação assíncrona for concluída, ela tentará continuar no thread da interface do usuário capturado.
- O thread da interface do usuário está bloqueado aguardando a conclusão da operação.
- O impasse ocorre porque nenhuma das operações pode prosseguir.
Operações de threads cruzadas
Quando precisar atualizar os controles da interface do usuário a partir de threads em segundo plano em operações assíncronas, use as técnicas de empacotamento apropriadas. Compreender a diferença entre abordagens de bloqueio e não bloqueio é crucial para aplicativos responsivos.
.NET 9 introduziu Control.InvokeAsync, que fornece marshaling compatível com async para a thread de interface do utilizador. Ao contrário Control.Invoke do que envia (bloqueia o thread de chamada), Control.InvokeAsynccoloca (sem bloqueio) na fila de mensagens do thread da interface do utilizador. Para obter mais informações sobre o Control.InvokeAsync, consulte Como fazer chamadas thread-safe para controles.
Principais vantagens do InvokeAsync:
- Sem bloqueio: retorna imediatamente, permitindo que o thread de chamada continue.
-
Async-friendly: retorna um
Taskque pode ser aguardado. - Propagação de exceções: propaga corretamente as exceções de volta para o código de chamada.
-
Suporte de cancelamento: Suporta
CancellationTokenpara cancelamento de operação.
private async void processButton_Click(object sender, EventArgs e)
{
processButton.Enabled = false;
// Start background work
await Task.Run(async () =>
{
for (int i = 0; i <= 100; i += 10)
{
// Simulate work
await Task.Delay(200);
// Create local variable to avoid closure issues
int currentProgress = i;
// Update UI safely from background thread
await progressBar.InvokeAsync(() =>
{
progressBar.Value = currentProgress;
statusLabel.Text = $"Progress: {currentProgress}%";
});
}
});
processButton.Enabled = true;
}
Private Async Sub processButton_Click(sender As Object, e As EventArgs) Handles processButton.Click
processButton.Enabled = False
' Start background work
Await Task.Run(Async Function()
For i As Integer = 0 To 100 Step 10
' Simulate work
Await Task.Delay(200)
' Create local variable to avoid closure issues
Dim currentProgress As Integer = i
' Update UI safely from background thread
Await progressBar.InvokeAsync(Sub()
progressBar.Value = currentProgress
statusLabel.Text = $"Progress: {currentProgress}%"
End Sub)
Next
End Function)
processButton.Enabled = True
End Sub
Para operações verdadeiramente assíncronas que precisam ser executadas no thread da interface do usuário:
private async void complexButton_Click(object sender, EventArgs e)
{
// This runs on UI thread but doesn't block it
statusLabel.Text = "Starting complex operation...";
// Dispatch and run on a new thread
await Task.WhenAll(Task.Run(SomeApiCallAsync),
Task.Run(SomeApiCallAsync),
Task.Run(SomeApiCallAsync));
// Update UI directly since we're already on UI thread
statusLabel.Text = "Operation completed";
}
private async Task SomeApiCallAsync()
{
using var client = new HttpClient();
// Simulate random network delay
await Task.Delay(Random.Shared.Next(500, 2500));
// Do I/O asynchronously
string result = await client.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md");
// Marshal back to UI thread
await this.InvokeAsync(async (cancelToken) =>
{
loggingTextBox.Text += $"{Environment.NewLine}Operation finished at: {DateTime.Now:HH:mm:ss.fff}";
});
// Do more async I/O ...
}
Private Async Sub complexButton_Click(sender As Object, e As EventArgs) Handles complexButton.Click
'Convert the method to enable the extension method on the type
Dim method = DirectCast(AddressOf ComplexButtonClickLogic,
Func(Of CancellationToken, Task))
'Invoke the method asynchronously on the UI thread
Await Me.InvokeAsync(method.AsValueTask())
End Sub
Private Async Function ComplexButtonClickLogic(token As CancellationToken) As Task
' This runs on UI thread but doesn't block it
statusLabel.Text = "Starting complex operation..."
' Dispatch and run on a new thread
Await Task.WhenAll(Task.Run(AddressOf SomeApiCallAsync),
Task.Run(AddressOf SomeApiCallAsync),
Task.Run(AddressOf SomeApiCallAsync))
' Update UI directly since we're already on UI thread
statusLabel.Text = "Operation completed"
End Function
Private Async Function SomeApiCallAsync() As Task
Using client As New HttpClient()
' Simulate random network delay
Await Task.Delay(Random.Shared.Next(500, 2500))
' Do I/O asynchronously
Dim result As String = Await client.GetStringAsync("https://github.com/dotnet/docs/raw/refs/heads/main/README.md")
' Marshal back to UI thread
' Extra work here in VB to handle ValueTask conversion
Await Me.InvokeAsync(DirectCast(
Async Function(cancelToken As CancellationToken) As Task
loggingTextBox.Text &= $"{Environment.NewLine}Operation finished at: {DateTime.Now:HH:mm:ss.fff}"
End Function,
Func(Of CancellationToken, Task)).AsValueTask() 'Extension method to convert Task
)
' Do more Async I/O ...
End Using
End Function
Sugestão
O .NET 9 inclui avisos do analisador (WFO2001) para ajudar a detetar quando métodos assíncronos são passados incorretamente para sobrecargas síncronas de InvokeAsync. Isso ajuda a evitar o comportamento de "lançar e esquecer".
Melhores práticas
- Use async/await consistentemente: não misture padrões assíncronos com chamadas de bloqueio.
-
Manipular exceções: sempre encapsular operações assíncronas em blocos try-catch em
async voidmanipuladores de eventos. - Fornecer comentários do usuário: atualize a interface do usuário para mostrar o progresso ou o status da operação.
- Desabilitar controles durante as operações: impeça que os usuários iniciem várias operações.
- Use CancellationToken: suporte ao cancelamento de operações para tarefas de longa execução.
- Considere ConfigureAwait(false): use no código da biblioteca para evitar capturar o contexto da UI quando não é necessário.
Conteúdo relacionado
.NET Desktop feedback