Delen via


Overzicht van gebeurtenissen

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 Task die kan worden verwacht.
  • Uitzonderingsdoorgifte: uitzonderingen worden correct doorgegeven aan de aanroepende code.
  • Annuleringsondersteuning: ondersteunt CancellationToken voor 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.

Opmerking

Als u Visual Basic gebruikt, heeft het vorige codefragment een extensiemethode gebruikt om een ValueTask naar een Taskte converteren. De code van de extensiemethode is beschikbaar op GitHub.

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 void gebeurtenis-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.