Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Un evento è un'azione a cui è possibile rispondere o "handle" nel codice. Gli eventi vengono in genere generati da un'azione dell'utente, ad esempio facendo clic sul mouse o premendo un tasto, ma possono anche essere generati dal codice del programma o dal sistema.
Le applicazioni guidate dagli eventi eseguono codice in risposta a un evento. Ogni modulo e controllo espone un set predefinito di eventi a cui è possibile rispondere. Se uno di questi eventi viene generato e esiste un gestore eventi associato, il gestore viene richiamato e viene eseguito il codice.
I tipi di eventi generati da un oggetto variano, ma molti tipi sono comuni alla maggior parte dei controlli. Ad esempio, la maggior parte degli oggetti ha un Click evento generato quando un utente fa clic su di esso.
Annotazioni
Molti eventi si verificano con altri eventi. Ad esempio, nel corso dell'evento DoubleClick che si verifica, si verificano gli eventi MouseDown, MouseUpe Click.
Per informazioni generali su come generare e utilizzare un evento, vedere Gestione e generazione di eventi in .NET.
Delegati e loro ruolo
I delegati sono classi comunemente usate all'interno di .NET per creare meccanismi di gestione degli eventi. I delegati equivalgono approssimativamente ai puntatori a funzione, comunemente usati in Visual C++ e in altri linguaggi orientati agli oggetti. A differenza dei puntatori a funzione, tuttavia, i delegati sono orientati agli oggetti, indipendenti dai tipi e sicuri. Inoltre, dove un puntatore a funzione contiene solo un riferimento a una funzione specifica, un delegato è costituito da un riferimento a un oggetto e fa riferimento a uno o più metodi all'interno dell'oggetto .
Questo modello di evento usa delegati per associare eventi ai metodi usati per gestirli. Il delegato consente ad altre classi di registrarsi per la notifica degli eventi specificando un metodo del gestore. Quando si verifica l'evento, il delegato chiama il metodo associato. Per altre informazioni su come definire i delegati, vedere Gestione e generazione di eventi.
I delegati possono essere associati a un singolo metodo o a più metodi; in questo caso si parla di multicast. Quando si crea un delegato per un evento, di solito si tratta di un evento multicast. Un'eccezione rara potrebbe essere un evento che comporta una procedura specifica ,ad esempio la visualizzazione di una finestra di dialogo, che non ripeterebbe logicamente più volte per evento. Per informazioni su come creare un delegato multicast, vedere Come combinare delegati (delegati multicast).
Un delegato multicast mantiene un elenco chiamate dei metodi associati. Il delegato multicast supporta un metodo Combine per aggiungere un metodo all'elenco chiamate e un metodo Remove per rimuoverlo.
Quando un'applicazione registra un evento, il controllo genera l'evento richiamando il delegato per tale evento. Il delegato chiama a sua volta il metodo associato. Nel caso più comune (un delegato multicast), il delegato chiama ogni metodo associato a turno nell'elenco di chiamate, fornendo una notifica uno-a-molti. Questa strategia significa che il controllo non deve mantenere un elenco di oggetti di destinazione per la notifica degli eventi. Il delegato gestisce tutte le registrazioni e le notifiche.
I delegati consentono anche l'associazione di più eventi allo stesso metodo, consentendo una notifica molti-a-uno. Ad esempio, un evento di clic del pulsante e un evento di clic del comando del menu possono invocare lo stesso delegato, il quale a sua volta chiama un unico metodo per gestire questi eventi distinti nello stesso modo.
Il meccanismo di associazione usato con i delegati è dinamico: un delegato può essere associato in fase di esecuzione a qualsiasi metodo la cui firma corrisponde a quella del gestore eventi. Con questa funzionalità, è possibile configurare o modificare il metodo associato a seconda di una condizione e associare dinamicamente un gestore eventi a un controllo.
Eventi in Windows Form
Gli eventi in Windows Form vengono dichiarati con il delegato per i EventHandler<TEventArgs> metodi del gestore. Ogni gestore eventi fornisce due parametri che consentono di gestire correttamente l'evento. Nell'esempio seguente viene illustrato un gestore eventi per l'evento Button di un controllo 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)
{
}
Il primo parametro,sender, fornisce un riferimento all'oggetto che ha generato l'evento. Il secondo parametro, e, passa un oggetto specifico all'evento gestito. Facendo riferimento alle proprietà dell'oggetto (e, a volte, ai relativi metodi), è possibile ottenere informazioni come la posizione del cursore durante eventi del mouse o i dati trasferiti in eventi di trascinamento della selezione.
In genere ogni evento produce un gestore eventi con un tipo di oggetto evento diverso per il secondo parametro. Alcuni gestori eventi, ad esempio quelli per gli eventi MouseDown e MouseUp, hanno lo stesso tipo di oggetto per il secondo parametro. Per questi tipi di eventi, è possibile usare lo stesso gestore eventi per gestire entrambi gli eventi.
È anche possibile usare lo stesso gestore eventi per gestire lo stesso evento per controlli diversi. Ad esempio, se si dispone di un gruppo di RadioButton controlli in una maschera, è possibile creare un singolo gestore eventi per l'evento Click di ogni RadioButtonoggetto. Per altre informazioni, vedere Come gestire un evento di controllo.
Gestori eventi asincroni
Le applicazioni moderne spesso devono eseguire operazioni asincrone in risposta alle azioni dell'utente, ad esempio il download di dati da un servizio Web o l'accesso ai file. I gestori eventi di Windows Form possono essere dichiarati come async metodi per supportare questi scenari, ma esistono importanti considerazioni per evitare problemi comuni.
Modello di gestore eventi asincrono di base
I gestori eventi possono essere dichiarati con il async modificatore (Async in Visual Basic) e usare await (Await in Visual Basic) per le operazioni asincrone. Poiché i gestori eventi devono restituire void (o essere dichiarati come in Sub Visual Basic), sono uno dei rari usi accettabili di async void (o Async Sub in 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
Sebbene async void sia sconsigliato, è necessario per i gestori eventi (e il codice simile al gestore eventi, ad esempio Control.OnClick) perché non possono restituire Task. Eseguire sempre il wrapping delle operazioni attese in try-catch blocchi per gestire correttamente le eccezioni, come illustrato nell'esempio precedente.
Problemi comuni e deadlock
Avvertimento
Non usare mai chiamate di blocco come .Wait(), .Resulto .GetAwaiter().GetResult() nei gestori eventi o in qualsiasi codice dell'interfaccia utente. Questi modelli possono causare deadlock.
Il codice seguente illustra un anti-pattern comune che causa deadlock:
// 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
Questo causa un deadlock per i motivi seguenti:
- Il thread dell'interfaccia utente chiama il metodo asincrono e blocca l'attesa del risultato.
- Il metodo asincrono acquisisce il thread dell'interfaccia
SynchronizationContextutente. - Al termine dell'operazione asincrona, tenta di continuare nel thread dell'interfaccia utente acquisito.
- Il thread dell'interfaccia utente è bloccato in attesa del completamento dell'operazione.
- Il deadlock si verifica perché nessuna operazione può continuare.
Operazioni tra thread
Quando è necessario aggiornare i controlli dell'interfaccia utente dai thread in background all'interno di operazioni asincrone, usare le tecniche di marshalling appropriate. Comprendere la differenza tra approcci bloccante e non bloccante è fondamentale per le applicazioni reattive.
.NET 9 ha introdotto Control.InvokeAsync, che fornisce il marshalling asincrono al thread dell'interfaccia utente. A differenza Control.Invoke del quale invia (blocca il thread chiamante), Control.InvokeAsyncinvia (non bloccante) alla coda dei messaggi del thread dell'interfaccia utente. Per altre informazioni su Control.InvokeAsync, vedere Come effettuare chiamate thread-safe ai controlli.
Vantaggi principali di InvokeAsync:
- Non bloccante: restituisce immediatamente, consentendo al thread chiamante di continuare.
-
Async-friendly: restituisce un oggetto
Taskche può essere atteso. - Propagazione delle eccezioni: propaga correttamente le eccezioni al codice chiamante.
-
Supporto per l'annullamento dell'annullamento: supporta
CancellationTokenl'annullamento dell'operazione.
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
Per operazioni realmente asincrone che devono essere eseguite nel thread dell'interfaccia utente:
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
Suggerimento
.NET 9 include avvisi dell'analizzatore (WFO2001) per rilevare quando i metodi asincroni vengono passati in modo non corretto agli overload sincroni di InvokeAsync. Ciò consente di evitare il comportamento "fire-and-forget".
Procedure consigliate
- Usare in modo coerente async/await: non combinare modelli asincroni con chiamate di blocco.
-
Gestire le eccezioni: eseguire sempre il wrapping delle operazioni asincrone nei blocchi try-catch nei
async voidgestori eventi. - Fornire commenti e suggerimenti degli utenti: aggiornare l'interfaccia utente per visualizzare lo stato o lo stato dell'operazione.
- Disabilitare i controlli durante le operazioni: impedire agli utenti di avviare più operazioni.
- Usare CancellationToken: supportare l'annullamento dell'operazione per le attività a esecuzione prolungata.
- Prendere in considerazione ConfigureAwait(false): usare nel codice della libreria per evitare di acquisire il contesto dell'interfaccia utente quando non è necessario.
Contenuti correlati
.NET Desktop feedback