Aracılığıyla paylaş


Olaylara Genel Bakış

Olay, kodda yanıt verebileceğiniz veya "işleyebileceğiniz" bir eylemdir. Olaylar genellikle fareye tıklama veya bir tuşa basma gibi bir kullanıcı eylemi tarafından oluşturulur, ancak program kodu veya sistem tarafından da oluşturulabilir.

Olay temelli uygulamalar bir olaya yanıt olarak kod çalıştırır. Her form ve denetim, yanıt verebileceğiniz önceden tanımlanmış bir olay kümesini kullanıma sunar. Bu olaylardan biri oluşturulursa ve ilişkili bir olay işleyicisi varsa, işleyici çağrılır ve kod çalıştırılır.

Bir nesne tarafından tetiklenen olay türleri farklılık gösterir, ancak çoğu denetimde birçok tür yaygındır. Örneğin, çoğu nesnenin Click bir kullanıcı buna tıkladığında tetiklenen bir olayı vardır.

Uyarı

Birçok olay diğer olaylarla gerçekleşir. Örneğin, gerçekleşen DoubleClick olayı sırasında, MouseDown, MouseUpve Click olayları gerçekleşir.

Bir olayı oluşturma ve kullanma hakkında genel bilgi için bkz. .NET'te olayları işleme ve oluşturma.

Temsilciler ve rolleri

Temsilciler, olay işleme mekanizmaları oluşturmak için .NET içinde yaygın olarak kullanılan sınıflardır. Delegeler, Visual C++ ve diğer nesne yönelimli dillerde yaygın olarak kullanılan işlev işaretçilerine kabaca eşittir. Ancak işlev işaretçilerinin aksine, temsilciler nesne odaklı, tür açısından güvenli ve güvenlidir. Ayrıca, işlev işaretçisi yalnızca belirli bir işleve başvuru içerdiğinde, temsilci bir nesneye başvurudan ve nesne içindeki bir veya daha fazla yönteme başvurudan oluşur.

Bu olay modeli, olayları işlemek için kullanılan yöntemlere bağlamak amacıyla temsilcilerini kullanır. Temsilci, bir işleyici yöntemi belirterek diğer sınıfların olay bildirimine kaydolmasını sağlar. Olay gerçekleştiğinde, delege bağlı yöntemi çağırır. Temsilcileri tanımlama hakkında daha fazla bilgi için bkz.olayları işleme ve oluşturma .

Delege, tek bir yönteme veya çok yönlü yayım olarak adlandırılan birden çok yönteme bağlanabilir. Bir olay için temsilci oluşturduğunuzda, genellikle çok yayınlı bir olay oluşturursunuz. Nadir bir özel durum, olay başına mantıksal olarak birden çok kez yinelenmeyecek belirli bir yordamla (iletişim kutusu görüntüleme gibi) sonuçlanan bir olay olabilir. Çoklu yayın temsilcisi oluşturma hakkında bilgi için bkz. Temsilcileri birleştirme (Çoklu Yayın Temsilcileri).

Çok noktaya yayın temsilcisi, ona bağlı yöntemlerin çağrı listesini tutar. Çok noktaya yayın temsilcisi, çağırma listesine bir yöntem eklemek için Combine yöntemini ve kaldırmak için Remove yöntemini destekler.

Bir uygulama bir olayı kaydettiğinde, denetim bu olay için temsilciyi çağırarak olayı tetikler. Temsilci sırasıyla bağlı yöntemi çağırır. En yaygın durumda (çok noktaya yayın temsilcisi), temsilci çağrı listesindeki her ilişkili yöntemi sırayla çağırır ve bu da bire çok bildirim yapılmasını sağlar. Bu strateji, denetimin olay bildirimi için hedef nesnelerin listesini tutmasına gerek olmadığı anlamına gelir; temsilci tüm kayıt ve bildirimi işler.

Temsilciler, birden fazla olayın aynı metoda bağlanmasına imkân tanıyarak çoktan bire bir bilgilendirme sağlar. Örneğin, bir düğme tıklama olayı ve menü-komut tıklama olayı aynı temsilciyi çağırabilir ve ardından bu ayrı olayları aynı şekilde işlemek için tek bir yöntem çağırır.

Temsilcilerle kullanılan bağlama mekanizması dinamiktir: Bir temsilci, çalışma zamanında imzası olay işleyicisiyle eşleşen herhangi bir yönteme bağlanabilir. Bu özellik sayesinde, bir koşula bağlı olarak bağlı yöntemi ayarlayabilir veya değiştirebilir ve denetime dinamik olarak bir olay işleyicisi ekleyebilirsiniz.

Windows Forms'daki olaylar

Windows Forms'daki olaylar, işleyici yöntemleri için EventHandler<TEventArgs> temsilci kullanılarak bildirilir. Her olay işleyicisi, olayı düzgün bir şekilde işlemenizi sağlayan iki parametre sağlar. Aşağıdaki örnekte, bir Button denetiminin Click olayı için bir olay işleyicisi gösterilmektedir.

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)
{

}

senderilk parametresi, olayı tetikleyen nesneye bir başvuru sağlar. İkinci parametre olan e, işlenen olaya özgü bir nesne geçirir. Nesnenin özelliklerine (ve bazen yöntemlerine) başvurarak fare olayları için farenin konumu veya sürükle ve bırak olaylarında aktarılan veriler gibi bilgileri alabilirsiniz.

Genellikle her olay, ikinci parametre için farklı bir olay nesnesi türüne sahip bir olay işleyicisi oluşturur. MouseDown ve MouseUp olayları için olanlar gibi bazı olay işleyicileri, ikinci parametreleri için aynı nesne türüne sahiptir. Bu tür olaylar için, her iki olayı işlemek için aynı olay işleyicisini kullanabilirsiniz.

Farklı denetimler için aynı olayı işlemek için aynı olay işleyicisini de kullanabilirsiniz. Örneğin, formda bir denetim grubunuz RadioButton varsa, her Clickolayı için RadioButton tek bir olay işleyicisi oluşturabilirsiniz. Daha fazla bilgi için bkz. Denetim olayını işleme.

Zaman uyumsuz olay işleyicileri

Modern uygulamaların genellikle web hizmetinden veri indirme veya dosyalara erişme gibi kullanıcı eylemlerine yanıt olarak zaman uyumsuz işlemler gerçekleştirmesi gerekir. Windows Forms olay işleyicileri bu senaryoları desteklemek için yöntemler olarak async bildirilebilir, ancak yaygın tuzaklardan kaçınmak için dikkat edilmesi gereken önemli noktalar vardır.

Temel zaman uyumsuz olay işleyici düzeni

Olay işleyicileri (Async Visual Basic'te) değiştiricisi ile async bildirilebilir ve zaman uyumsuz işlemler için (Await Visual Basic'te) kullanılabilir await . Olay işleyicilerinin döndürülmesi void (veya Visual Basic'te olarak Sub bildirilmesi) gerektiğinden, nadir kabul edilebilir kullanımlarından async void biridir (veya Async Sub Visual Basic'te):

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

Önemli

async void Önerilmez, ancak olay işleyicileri (ve gibi Control.OnClickolay işleyicisi benzeri kod) için gereklidir çünkü döndüremezlerTask. Önceki örnekte gösterildiği gibi özel durumları düzgün bir şekilde işlemek için her zaman beklenen işlemleri bloklar halinde try-catch sarmalayın.

Yaygın tuzaklar ve kilitlenmeler

Uyarı

, .Result.GetAwaiter().GetResult() veya gibi .Wait()engelleme çağrılarını hiçbir zaman olay işleyicilerinde veya kullanıcı arabirimi kodlarında kullanmayın. Bu desenler kilitlenmelere neden olabilir.

Aşağıdaki kod, kilitlenmelere neden olan yaygın bir anti-deseni gösterir:

// 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

Bu, aşağıdaki nedenlerden dolayı kilitlenmeye neden olur:

  • UI iş parçacığı zaman uyumsuz yöntemini çağırır ve sonucun beklemesini engeller.
  • Zaman uyumsuz yöntem ui iş parçacığının SynchronizationContextyakalar.
  • Zaman uyumsuz işlem tamamlandığında, yakalanan ui iş parçacığında devam etmeye çalışır.
  • kullanıcı arabirimi iş parçacığı işlemin tamamlanmasını beklerken engellendi.
  • Hiçbir işlem devam etemediği için kilitlenme oluşur.

İş parçacıkları arası işlemler

Zaman uyumsuz işlemler içindeki arka plan iş parçacıklarından kullanıcı arabirimi denetimlerini güncelleştirmeniz gerektiğinde, uygun sıralama tekniklerini kullanın. Engelleyici ve engelleyici olmayan yaklaşımlar arasındaki farkı anlamak, duyarlı uygulamalar için çok önemlidir.

.NET 9, kullanıcı arabirimi iş parçacığına zaman uyumsuz hazırlama sağlayan ile kullanıma sunulmuştur Control.InvokeAsync. Bu işlemden farklı olarakControl.Invoke, Control.InvokeAsync kullanıcı arabirimi iş parçacığının ileti kuyruğuna gönderiler (engelleyici olmayan) gönderir (çağıran iş parçacığını engeller). hakkında Control.InvokeAsyncdaha fazla bilgi için bkz. Denetimlere iş parçacığı açısından güvenli çağrılar yapma.

InvokeAsync'in temel avantajları:

  • Engelleyici olmayan: Çağıran iş parçacığının devam etmesi için hemen döndürür.
  • Zaman uyumsuz: Beklenebilen bir Task döndürür.
  • Özel durum yayma: Özel durumları çağıran koda doğru şekilde yayılım.
  • İptal desteği: İşlem iptali için destekler CancellationToken .
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

Kullanıcı arabirimi iş parçacığında çalıştırılması gereken gerçekten zaman uyumsuz işlemler için:

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

Tavsiye

.NET 9, zaman uyumsuz yöntemlerin zaman uyumlu aşırı yüklemelerine InvokeAsyncyanlış geçirildiğini algılamaya yardımcı olmak için çözümleyici uyarıları (WFO2001) içerir. Bu, "yangın ve unutma" davranışını önlemeye yardımcı olur.

Uyarı

Visual Basic kullanıyorsanız, önceki kod parçacığı uzantısını bir öğesine dönüştürmek ValueTask için bir Taskuzantı yöntemi kullandı. Uzantı yöntemi kodu GitHub'da kullanılabilir.

En iyi yöntemler

  • Sürekli olarak async/await kullanın: Zaman uyumsuz desenleri engelleme çağrılarıyla karıştırmayın.
  • Özel durumları işleme: Olay işleyicilerindeki try-catch bloklarında async void her zaman zaman uyumsuz işlemleri sarmalayın.
  • Kullanıcı geri bildirimi sağlayın: İşlem ilerleme durumunu veya durumunu göstermek için kullanıcı arabirimini güncelleştirin.
  • İşlemler sırasında denetimleri devre dışı bırakma: Kullanıcıların birden çok işlem başlatmasını engelleyin.
  • CancellationToken kullanma: Uzun süre çalışan görevler için işlem iptali desteği.
  • ConfigureAwait(false): Gerekli olmadığında kullanıcı arabirimi bağlamını yakalamaktan kaçınmak için kitaplık kodunda kullanın.