Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Een gebeurtenis is een actie die u in code kunt beantwoorden of 'verwerken'. Gebeurtenissen worden meestal gegenereerd door een gebruikersactie, zoals klikken op de muis of op een toets drukken, maar ze kunnen ook worden gegenereerd door programmacode of door het systeem.
Gebeurtenisgestuurde toepassingen voeren code uit als reactie op een gebeurtenis. Elk formulier en besturingselement bevat een vooraf gedefinieerde set gebeurtenissen waarop u kunt reageren. Als een van deze gebeurtenissen wordt gegenereerd en er een bijbehorende gebeurtenis-handler is, wordt de handler aangeroepen en wordt de code uitgevoerd.
De typen gebeurtenissen die door een object worden gegenereerd, variëren, maar veel typen zijn gebruikelijk voor de meeste besturingselementen. De meeste objecten hebben bijvoorbeeld een Click gebeurtenis die wordt gegenereerd wanneer een gebruiker erop klikt.
Opmerking
Veel gebeurtenissen vinden plaats met andere gebeurtenissen. In de loop van de DoubleClick gebeurtenis die zich voordoet, vinden bijvoorbeeld de MouseDown, MouseUpen Click gebeurtenissen plaats.
Zie Afhandeling en het genereren van gebeurtenissen in .NET voor algemene informatie over het genereren en gebruiken van een gebeurtenis.
Gemachtigden en hun rol
Gedelegeerden zijn klassen die vaak worden gebruikt in .NET om mechanismen voor gebeurtenisafhandeling te bouwen. Delegaten zijn vrijwel gelijk aan functieverwijzingen, die vaak worden gebruikt in Visual C++ en andere objectgeoriënteerde programmeertalen. In tegenstelling tot functiewijzers zijn delegeren echter objectgeoriënteerd, typveilig en veilig. Wanneer een functieaanwijzer alleen een verwijzing naar een bepaalde functie bevat, bestaat een gedelegeerde uit een verwijzing naar een object en verwijzingen naar een of meer methoden binnen het object.
Dit gebeurtenismodel gebruikt gemachtigden om gebeurtenissen te binden aan de methoden die worden gebruikt om ze te verwerken. Met de delegate kunnen andere klassen zich registreren voor evenementmeldingen door een handlermethode op te geven. Wanneer de gebeurtenis plaatsvindt, roept de gedelegeerde de gebonden methode aan. Voor meer informatie over het definiëren van gedelegeerden, zie Het verwerken en het afhandelen van gebeurtenissen.
Gemachtigden kunnen worden gebonden aan één methode of aan meerdere methoden, ook wel multicasting genoemd. Wanneer u een delegaat voor een gebeurtenis maakt, maakt u doorgaans een multicast-evenement. Een zeldzame uitzondering kan een gebeurtenis zijn die resulteert in een specifieke procedure (zoals het weergeven van een dialoogvenster) die niet logisch meerdere keren per gebeurtenis zou worden herhaald. Zie How to combine delegates (Multicast Delegates)voor meer informatie over het maken van een multicastdelegatie.
Een multicast-gemachtigde onderhoudt een aanroeplijst van de methoden die eraan zijn gebonden. De multicast-gemachtigde ondersteunt een Combine methode om een methode toe te voegen aan de aanroeplijst en een Remove methode om deze te verwijderen.
Wanneer een toepassing een gebeurtenis registreert, genereert het besturingselement de gebeurtenis door de gemachtigde voor die gebeurtenis aan te roepen. De gedelegeerde roept op zijn beurt de gebonden methode aan. In het meest voorkomende geval (een multicastdelegatie) roept de gedelegeerde elke afhankelijke methode aan in de aanroeplijst, die een een-op-veel-melding biedt. Deze strategie betekent dat het besturingselement geen lijst met doelobjecten hoeft te onderhouden voor gebeurtenismeldingen. De gedelegeerde verwerkt alle registratie en meldingen.
Gedelegeerden zorgen er ook voor dat meerdere gebeurtenissen aan dezelfde methode worden gebonden, waardoor een veel-op-een-melding wordt toegestaan. Een knopklikgebeurtenis en een menuopdrachtklikgebeurtenis kunnen beide dezelfde gedelegeerde aanroepen, die vervolgens één methode oproept om deze gebeurtenissen op dezelfde wijze af te handelen.
Het bindingsmechanisme dat wordt gebruikt met gemachtigden is dynamisch: een gemachtigde kan tijdens runtime worden gebonden aan elke methode waarvan de handtekening overeenkomt met die van de gebeurtenis-handler. Met deze functie kunt u de gebonden methode instellen of wijzigen, afhankelijk van een voorwaarde, en een gebeurtenis-handler dynamisch koppelen aan een controle-element.
Gebeurtenissen in Windows Forms
Gebeurtenissen in Windows Forms worden gedeclareerd met de EventHandler<TEventArgs> delegate voor methoden van handlers. Elke gebeurtenis-handler biedt twee parameters waarmee u de gebeurtenis correct kunt verwerken. In het volgende voorbeeld ziet u een gebeurtenis-handler voor een Button-gebeurtenis van een Click-besturingselement.
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)
{
}
De eerste parameter,sender, bevat een verwijzing naar het object dat de gebeurtenis heeft gegenereerd. Met de tweede parameter wordt eeen object doorgegeven dat specifiek is voor de gebeurtenis die wordt verwerkt. Door te verwijzen naar de eigenschappen van het object (en soms de bijbehorende methoden), kunt u informatie verkrijgen, zoals de locatie van de muis voor muisgebeurtenissen of gegevens die worden overgebracht in slepen en neerzetten.
Normaal gesproken produceert elke gebeurtenis een gebeurtenis-handler met een ander gebeurtenisobjecttype voor de tweede parameter. Sommige gebeurtenis-handlers, zoals die voor de MouseDown en MouseUp gebeurtenissen, hebben hetzelfde objecttype voor hun tweede parameter. Voor deze typen gebeurtenissen kunt u dezelfde gebeurtenis-handler gebruiken om beide gebeurtenissen af te handelen.
U kunt dezelfde gebeurtenishandler ook gebruiken om dezelfde gebeurtenis voor verschillende besturingselementen te verwerken. Als u bijvoorbeeld een groep RadioButton besturingselementen op een formulier hebt, kunt u één gebeurtenis-handler maken voor de Click gebeurtenis van elke RadioButton. Zie Een besturings gebeurtenis afhandelen voor meer informatie.
Asynchrone gebeurtenis-handlers
Moderne toepassingen moeten vaak asynchrone bewerkingen uitvoeren als reactie op gebruikersacties, zoals het downloaden van gegevens uit een webservice of het openen van bestanden. Windows Forms-gebeurtenis-handlers kunnen worden gedeclareerd als async methoden om deze scenario's te ondersteunen, maar er zijn belangrijke overwegingen om veelvoorkomende valkuilen te voorkomen.
Eenvoudige asynchrone gebeurtenis-handlerpatroon
Gebeurtenis-handlers kunnen worden gedeclareerd met de async modifier (Async in Visual Basic) en het gebruik await (Await in Visual Basic) voor asynchrone bewerkingen. Aangezien gebeurtenis-handlers moeten worden geretourneerd void (of als een Sub in Visual Basic moeten worden gedeclareerd), zijn ze een van de zeldzame acceptabele toepassingen van async void (of 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
Belangrijk
Hoewel async void dit wordt afgeraden, is het noodzakelijk voor gebeurtenis-handlers (en gebeurtenis-handlerachtige code, zoals Control.OnClick) omdat ze niet kunnen retourneren Task. Verpakte bewerkingen altijd in try-catch blokken om uitzonderingen correct af te handelen, zoals wordt weergegeven in het vorige voorbeeld.
Veelvoorkomende valkuilen en impasses
Waarschuwing
Gebruik nooit blokkerende aanroepen zoals .Wait(), .Resultof .GetAwaiter().GetResult() in gebeurtenishandlers of in ui-code. Deze patronen kunnen impasses veroorzaken.
De volgende code demonstreert een veelvoorkomend antipatroon dat impasses veroorzaakt:
// 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
Dit veroorzaakt een impasse om de volgende redenen:
- De UI-thread roept de asynchrone methode aan en blokkeert het wachten op het resultaat.
- Met de asynchrone methode worden de threads van
SynchronizationContextde gebruikersinterface vastgelegd. - Wanneer de asynchrone bewerking is voltooid, wordt geprobeerd door te gaan op de vastgelegde UI-thread.
- De UI-thread wordt geblokkeerd totdat de bewerking is voltooid.
- Impasse treedt op omdat geen van beide bewerkingen kan worden voortgezet.
Bewerkingen voor meerdere threads
Wanneer u UI-besturingselementen van achtergrondthreads binnen asynchrone bewerkingen wilt bijwerken, gebruikt u de juiste marshalingtechnieken. Het verschil tussen blokkerende en niet-blokkerende benaderingen is van cruciaal belang voor responsieve toepassingen.
.NET 9 geïntroduceerd Control.InvokeAsync, dat asynchroon marshaling biedt aan de UI-thread. In tegenstelling tot Control.Invoke het verzenden (blokkeert de aanroepende thread), Control.InvokeAsyncberichten (niet-blokkerend) naar de berichtenwachtrij van de gebruikersinterfacethread. Zie Thread-Safe-aanroepen naar besturingselementen voor meer informatie.Control.InvokeAsync
Belangrijkste voordelen van InvokeAsync:
- Niet-blokkerend: retourneert onmiddellijk, zodat de aanroepende thread kan worden voortgezet.
-
Asynchroon: retourneert een
Taskdie kan worden verwacht. - Uitzonderingsdoorgifte: uitzonderingen worden correct doorgegeven aan de aanroepende code.
-
Annuleringsondersteuning: ondersteunt
CancellationTokenvoor annulering van bewerkingen.
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
Voor echt asynchrone bewerkingen die moeten worden uitgevoerd op de UI-thread:
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
Aanbeveling
.NET 9 bevat analysewaarschuwingen (WFO2001) om te detecteren wanneer asynchrone methoden onjuist worden doorgegeven aan synchrone overbelastingen van InvokeAsync. Zo voorkomt u 'fire-and-forget'-gedrag.
Beste praktijken
- Gebruik async/await consistent: combineer asynchrone patronen niet met blokkerende aanroepen.
-
Uitzonderingen verwerken: asynchrone bewerkingen altijd verpakken in try-catch-blokken in
async voidgebeurtenis-handlers. - Feedback van gebruikers geven: werk de gebruikersinterface bij om de voortgang of status van de bewerking weer te geven.
- Besturingselementen uitschakelen tijdens bewerkingen: voorkomen dat gebruikers meerdere bewerkingen starten.
- Gebruik CancellationToken: Annulering van ondersteuningsbewerking voor langlopende taken.
- Overweeg ConfigureAwait(false): gebruik in bibliotheekcode om te voorkomen dat de UI-context wordt vastgelegd wanneer dat niet nodig is.
Verwante inhoud
.NET Desktop feedback