Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Bei einem Ereignis handelt es sich um eine Aktion, auf die Sie im Code reagieren oder "behandeln" können. Ereignisse werden in der Regel von einer Benutzeraktion generiert, z. B. durch Klicken auf die Maus oder durch Drücken einer Taste, aber sie können auch durch Programmcode oder vom System generiert werden.
Ereignisgesteuerte Anwendungen führen Code als Reaktion auf ein Ereignis aus. Jedes Formular und jedes Steuerelement macht einen vordefinierten Satz von Ereignissen verfügbar, auf den Sie reagieren können. Wenn eines dieser Ereignisse ausgelöst wird und ein zugeordneter Ereignishandler vorhanden ist, wird der Handler aufgerufen und Code ausgeführt.
Die Typen von Ereignissen, die von einem Objekt ausgelöst werden, variieren, aber viele Typen sind für die meisten Steuerelemente üblich. Die meisten Objekte verfügen beispielsweise über ein Click Ereignis, das ausgelöst wird, wenn ein Benutzer darauf klickt.
Hinweis
Viele Ereignisse treten mit anderen Ereignissen auf. Beispielsweise treten im Verlauf des DoubleClick-Ereignisses die MouseDown, MouseUp und Click-Ereignisse auf.
Allgemeine Informationen zum Auslösen und Nutzen eines Ereignisses finden Sie unter Behandeln und Auslösen von Ereignissen in .NET.
Stellvertretungen und deren Rolle
Delegaten sind Klassen, die in .NET häufig zur Implementierung von Ereignisbehandlungsmechanismen verwendet werden. Stellvertretungen entsprechen ungefähr Funktionszeigern, die häufig in Visual C++ und anderen objektorientierten Sprachen verwendet werden. Im Gegensatz zu Funktionszeigern sind Delegaten objektorientiert, typsicher und sicher. Wenn ein Funktionszeiger nur einen Verweis auf eine bestimmte Funktion enthält, besteht ein Delegat aus einem Verweis auf ein Objekt und verweisen auf eine oder mehrere Methoden innerhalb des Objekts.
Dieses Ereignismodell verwendet Delegaten, um Ereignisse an die Methoden zu binden, die sie verarbeiten. Der Delegat ermöglicht es anderen Klassen, sich für ereignisbenachrichtigungen zu registrieren, indem eine Handlermethode angegeben wird. Wenn das Ereignis auftritt, ruft der Delegate die gebundene Methode auf. Weitere Informationen zum Definieren von Stellvertretungen finden Sie unter Behandeln und Auslösen von Ereignissen.
Stellvertretungen können an eine einzelne Methode oder an mehrere Methoden gebunden werden, die als Multicasting bezeichnet werden. Beim Erstellen eines Delegaten für ein Ereignis erstellen Sie in der Regel ein Multicastereignis. Eine seltene Ausnahme kann ein Ereignis sein, das zu einer bestimmten Prozedur führt (z. B. das Anzeigen eines Dialogfelds), das nicht logisch mehrmals pro Ereignis wiederholt würde. Informationen darüber, wie Sie einen Multicast-Delegaten erstellen, finden Sie unter Delegaten kombinieren (Multicast-Delegaten).
Ein Multicastdelegat verwaltet eine Aufrufliste der an sie gebundenen Methoden. Der Multicastdelegat unterstützt eine Combine Methode, um eine Methode zur Aufrufliste hinzuzufügen und eine Remove Methode, um sie zu entfernen.
Wenn eine Anwendung ein Ereignis aufzeichnet, löst das Steuerelement das Ereignis aus, indem er den Delegaten für dieses Ereignis aufruft. Der Delegat ruft wiederum die gebundene Methode auf. Im häufigsten Fall (ein Multicastdelegat) ruft der Delegat jede gebundene Methode in der Aufrufliste auf, was eine Eins-zu-viele-Benachrichtigung bereitstellt. Diese Strategie bedeutet, dass das Steuerelement keine Liste der Zielobjekte für die Ereignisbenachrichtigung verwalten muss – der Delegat behandelt alle Registrierungen und Benachrichtigungen.
Delegaten ermöglichen außerdem, dass mehrere Ereignisse an dieselbe Methode gebunden werden können, wodurch eine Benachrichtigung von vielen auf einen möglich ist. Beispielsweise kann ein Schaltflächenklickereignis und ein Menübefehl-Click-Ereignis denselben Delegat aufrufen, der dann eine einzelne Methode aufruft, um diese separaten Ereignisse auf die gleiche Weise zu behandeln.
Der bindungsmechanismus, der mit Delegaten verwendet wird, ist dynamisch: Ein Delegat kann zur Laufzeit an jede Methode gebunden werden, deren Signatur mit der des Ereignishandlers übereinstimmt. Mit diesem Feature können Sie die gebundene Methode je nach Bedingung einrichten oder ändern und einen Ereignishandler dynamisch an ein Steuerelement anfügen.
Ereignisse in Windows Forms
Ereignisse in Windows Forms werden mit dem EventHandler<TEventArgs> Delegat für Handlermethoden deklariert. Jeder Ereignishandler stellt zwei Parameter bereit, mit denen Sie das Ereignis ordnungsgemäß behandeln können. Das folgende Beispiel zeigt einen Ereignishandler für das Ereignis eines Button Steuerelements 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)
{
}
Der erste Parametersender stellt einen Verweis auf das Objekt bereit, das das Ereignis ausgelöst hat. Der zweite Parameter eübergibt ein Objekt, das für das Ereignis spezifisch ist, das behandelt wird. Durch Verweisen auf die Eigenschaften des Objekts (und manchmal auch deren Methoden) können Sie Informationen abrufen, z. B. die Position der Maus für Mausereignisse oder Daten, die in Drag-and-Drop-Ereignissen übertragen werden.
In der Regel erzeugt jedes Ereignis einen Ereignishandler mit einem anderen Ereignisobjekttyp für den zweiten Parameter. Einige Ereignishandler, wie die für die MouseDown- und MouseUp-Ereignisse, haben denselben Objekttyp als zweiten Parameter. Für diese Ereignistypen können Sie denselben Ereignishandler verwenden, um beide Ereignisse zu behandeln.
Sie können auch denselben Ereignishandler verwenden, um dasselbe Ereignis für verschiedene Steuerelemente zu behandeln. Wenn Sie beispielsweise über eine Gruppe von RadioButton Steuerelementen in einem Formular verfügen, können Sie einen einzelnen Ereignishandler für das Click Ereignis jedes RadioButtonFormulars erstellen. Weitere Informationen finden Sie unter Umgang mit einem Steuerelementereignis.
Asynchrone Ereignishandler
Moderne Anwendungen müssen häufig asynchrone Vorgänge als Reaktion auf Benutzeraktionen ausführen, z. B. das Herunterladen von Daten aus einem Webdienst oder den Zugriff auf Dateien. Windows Forms-Ereignishandler können als async Methoden deklariert werden, um diese Szenarien zu unterstützen, aber es gibt wichtige Überlegungen, um häufige Fallstricke zu vermeiden.
Grundlegendes asynchrones Ereignishandlermuster
Ereignishandler können mit dem async Modifizierer (Asyncin Visual Basic) deklariert und (Await in Visual Basic) für asynchrone Vorgänge verwendet await werden. Da Ereignishandler zurückgegeben void werden müssen (oder als Sub in Visual Basic deklariert werden müssen), sind sie eine der seltenen zulässigen Verwendungen async void (oder 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
Von Bedeutung
Es async void wird zwar abgeraten, aber für Ereignishandler (und ereignishandlerähnlicher Code, z Control.OnClick. B. ) erforderlich, da sie nicht zurückgegeben Taskwerden können. Immer erwartete Vorgänge in try-catch Blöcken umschließen, um Ausnahmen ordnungsgemäß zu behandeln, wie im vorherigen Beispiel gezeigt.
Häufige Fallstricke und Deadlocks
Warnung
Verwenden Sie niemals Blockierungsaufrufe wie .Wait(), oder .Result.GetAwaiter().GetResult() in Ereignishandlern oder ui-Code. Diese Muster können Deadlocks verursachen.
Der folgende Code veranschaulicht ein gängiges Antimuster, das Deadlocks verursacht:
// 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
Dies führt zu einem Deadlock aus folgenden Gründen:
- Der UI-Thread ruft die asynchrone Methode auf und blockiert das Warten auf das Ergebnis.
- Die asynchrone Methode erfasst die UI-Threads
SynchronizationContext. - Nach Abschluss des asynchronen Vorgangs wird versucht, den erfassten UI-Thread fortzusetzen.
- Der UI-Thread wird blockiert, bis der Vorgang abgeschlossen ist.
- Deadlock tritt auf, da keine operation fortgesetzt werden kann.
Threadübergreifende Vorgänge
Wenn Sie UI-Steuerelemente aus Hintergrundthreads in asynchronen Vorgängen aktualisieren müssen, verwenden Sie die entsprechenden Marshaling-Techniken. Das Verständnis des Unterschieds zwischen Blockierungs- und nicht blockierenden Ansätzen ist für reaktionsfähige Anwendungen von entscheidender Bedeutung.
.NET 9 eingeführt Control.InvokeAsync, das asynchrone Marshaling für den UI-Thread bereitstellt. Im Gegensatz dazu Control.Invoke , welche Senden (den aufrufenden Thread blockiert), Control.InvokeAsyncBeiträge (nicht blockieren) an die Nachrichtenwarteschlange des UI-Threads. Weitere Informationen finden Control.InvokeAsyncSie unter How to make thread-safe calls to controls.
Wichtige Vorteile von InvokeAsync:
- Nicht blockierend: Gibt sofort zurück, sodass der aufrufende Thread fortgesetzt werden kann.
-
Asynchron-freundlich: Gibt ein
Task, das erwartet werden kann. - Ausnahmeverteilung: Gibt Ausnahmen ordnungsgemäß an den aufrufenden Code weiter.
-
Abbruchunterstützung: Unterstützt
CancellationTokenden Abbruch des Vorgangs.
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
Für wirklich asynchrone Vorgänge, die im UI-Thread ausgeführt werden müssen:
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
Tipp
.NET 9 enthält Analysewarnungen (WFO2001), um zu erkennen, wann asynchrone Methoden fälschlicherweise an synchrone Überladungen InvokeAsyncübergeben werden. Dies trägt dazu bei, das "Feuer-und-Vergessen"-Verhalten zu verhindern.
Bewährte Methoden
- Verwenden Sie async/await konsistent: Mischen Sie keine asynchronen Muster mit blockierungsaufrufen.
-
Behandeln von Ausnahmen: Schließen Sie asynchrone Vorgänge immer in Try-Catch-Blöcken in
async voidEreignishandlern um. - Geben Sie Benutzerfeedback an: Aktualisieren Sie die Benutzeroberfläche, um den Vorgangsfortschritt oder -status anzuzeigen.
- Deaktivieren Von Steuerelementen während vorgängen: Verhindern, dass Benutzer mehrere Vorgänge starten.
- Verwenden Sie CancellationToken: Support operation cancellation for long-running tasks.
- Erwägen Sie ConfigureAwait(false):Verwenden Sie in Bibliothekscode, um zu vermeiden, dass der Ui-Kontext bei Bedarf erfasst wird.
Verwandte Inhalte
.NET Desktop feedback