Bagikan melalui


Gambaran umum acara

Peristiwa adalah tindakan yang dapat Anda respons, atau "tangani," dalam kode. Peristiwa biasanya dihasilkan oleh tindakan pengguna, seperti mengklik mouse atau menekan tombol, tetapi juga dapat dihasilkan oleh kode program atau oleh sistem.

Aplikasi berbasis peristiwa menjalankan kode sebagai respons terhadap peristiwa. Setiap formulir dan kontrol mengekspos serangkaian peristiwa yang telah ditentukan sebelumnya yang dapat Anda tanggapi. Jika salah satu peristiwa ini dinaikkan dan ada penanganan aktivitas terkait, handler dipanggil dan kode dijalankan.

Jenis kejadian yang dihasilkan oleh sebuah objek bervariasi, tetapi banyak jenis yang umum pada sebagian besar kontrol. Misalnya, sebagian besar objek memiliki peristiwa Click yang dipicu saat pengguna mengkliknya.

Nota

Banyak peristiwa terjadi bersamaan dengan peristiwa lainnya. Misalnya, selama peristiwa DoubleClick terjadi, peristiwa MouseDown, MouseUp, dan Click terjadi.

Untuk informasi umum tentang cara memicu dan memanfaatkan peristiwa, lihat Menangani dan memicu peristiwa di .NET.

Delegasi dan perannya

Delegasi adalah kelas yang umum digunakan dalam .NET untuk membangun mekanisme penanganan peristiwa. Mendelegasikan kira-kira sama dengan penunjuk fungsi, umumnya digunakan dalam Visual C++ dan bahasa berorientasi objek lainnya. Namun tidak seperti penunjuk fungsi, delegat berorientasi objek, aman tipe, dan terjamin. Selain itu, di mana penunjuk fungsi hanya berisi referensi ke fungsi tertentu, delegasi terdiri dari referensi ke objek, dan mereferensikan ke satu atau beberapa metode dalam objek.

Model kejadian ini menggunakan mendelegasikan untuk mengikat peristiwa ke metode yang digunakan untuk menanganinya. Delegasi memungkinkan kelas lain mendaftar untuk pemberitahuan peristiwa dengan menentukan metode handler. Ketika peristiwa terjadi, delegasi memanggil metode terikat. Untuk informasi selengkapnya tentang cara menentukan delegasi, lihat Menangani dan meningkatkan peristiwa.

Delegasi dapat terikat ke satu metode atau ke beberapa metode, yang disebut sebagai multicasting. Saat membuat delegasi untuk suatu event, Anda biasanya membuat acara multicast. Pengecualian langka mungkin merupakan peristiwa yang menghasilkan prosedur tertentu (seperti menampilkan kotak dialog) yang tidak akan diulang secara logis beberapa kali per peristiwa. Untuk informasi tentang cara membuat delegasi multicast, lihat Cara menggabungkan delegasi (Delegasi Multicast).

Delegasi multicast mempertahankan daftar pemanggilan metode yang terikat padanya. Delegasi multicast mendukung metode Combine untuk menambahkan metode ke daftar pemanggilan dan metode Remove untuk menghapusnya.

Saat aplikasi merekam peristiwa, kontrol akan menaikkan peristiwa dengan memanggil delegasi untuk peristiwa tersebut. Delegasi pada gilirannya memanggil metode terikat. Dalam kasus yang paling umum (delegasi multicast), delegasi memanggil setiap metode terikat dalam daftar pemanggilan pada gilirannya, yang menyediakan pemberitahuan satu-ke-banyak. Strategi ini berarti bahwa kontrol tidak perlu mempertahankan daftar objek target untuk pemberitahuan peristiwa—delegasi menangani semua pendaftaran dan pemberitahuan.

Delegasi juga memungkinkan beberapa event terikat pada metode yang sama, memungkinkan notifikasi banyak ke satu. Misalnya, peristiwa klik tombol dan klik perintah menu dapat memicu delegasi yang sama, yang kemudian memanggil satu metode untuk memproses peristiwa ini dengan cara yang serupa.

Mekanisme pengikatan yang digunakan dengan delegat bersifat dinamis: delegat dapat diikat pada saat run-time ke metode apa pun yang tanda tangannya cocok dengan penangan kejadian. Dengan fitur ini, Anda dapat menyiapkan atau mengubah metode terikat tergantung pada kondisi dan melampirkan penanganan aktivitas secara dinamis ke kontrol.

Peristiwa dalam Formulir Windows

Peristiwa di Windows Forms dideklarasikan dengan EventHandler<TEventArgs> delegate untuk metode handler. Setiap penanganan aktivitas menyediakan dua parameter yang memungkinkan Anda menangani peristiwa dengan benar. Contoh berikut menunjukkan penanganan aktivitas untuk Button peristiwa kontrol 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)
{

}

Parameter pertama,sender memberikan referensi ke objek yang menaikkan peristiwa. Parameter kedua, e, meneruskan objek khusus untuk peristiwa yang sedang ditangani. Dengan merujuk pada properti objek (dan, terkadang, metodenya), Anda dapat memperoleh informasi seperti lokasi mouse untuk peristiwa mouse atau data yang ditransfer dalam peristiwa tarik dan lepas.

Biasanya setiap peristiwa menghasilkan penanganan aktivitas dengan jenis objek peristiwa yang berbeda untuk parameter kedua. Beberapa penangan kejadian, seperti untuk kejadian MouseDown dan MouseUp, memiliki jenis objek yang sama untuk parameter kedua. Untuk jenis peristiwa ini, Anda dapat menggunakan penanganan aktivitas yang sama untuk menangani kedua peristiwa.

Anda juga dapat menggunakan penanganan aktivitas yang sama untuk menangani peristiwa yang sama untuk kontrol yang berbeda. Misalnya, jika Anda memiliki sekelompok RadioButton kontrol pada formulir, Anda dapat membuat satu penanganan aktivitas untuk Click peristiwa setiap RadioButton. Untuk informasi selengkapnya, lihat Cara menangani peristiwa kontrol.

Penanganan aktivitas asinkron

Aplikasi modern sering kali perlu melakukan operasi asinkron sebagai respons terhadap tindakan pengguna, seperti mengunduh data dari layanan web atau mengakses file. Penanganan aktivitas Windows Forms dapat dinyatakan sebagai async metode untuk mendukung skenario ini, tetapi ada pertimbangan penting untuk menghindari jebakan umum.

Pola penanganan aktivitas asinkron dasar

Penanganan event dapat dideklarasikan dengan modifier async (Async di Visual Basic) dan menggunakan await (Await di Visual Basic) untuk operasi asinkron. Karena pengendali peristiwa harus mengembalikan void (atau dinyatakan sebagai Sub di Visual Basic), ini adalah salah satu penggunaan async void yang jarang dan dapat diterima (atau Async Sub di 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

Penting

Meskipun async void tidak disarankan, diperlukan untuk penangan kejadian (dan kode yang mirip penangan kejadian, seperti Control.OnClick) karena mereka tidak dapat mengembalikan Task. Selalu bungkus operasi yang menunggu dalam blok try-catch untuk menangani pengecualian dengan benar, seperti yang ditunjukkan pada contoh yang telah disebutkan sebelumnya.

Jebakan dan kebuntuan umum

Peringatan

Jangan pernah menggunakan panggilan pemblokiran seperti .Wait(), , .Resultatau .GetAwaiter().GetResult() di penanganan aktivitas atau kode UI apa pun. Pola-pola ini dapat menyebabkan kebuntuan.

Kode berikut menunjukkan anti-pola umum yang menyebabkan kebuntuan:

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

Hal ini menyebabkan kebuntuan karena alasan berikut:

  • Thread UI memanggil metode asinkron dan memblokir sambil menunggu hasilnya.
  • Metode asinkron menangkap UI utas SynchronizationContext.
  • Ketika operasi asinkron selesai, ia mencoba untuk melanjutkan pada utas UI yang ditangkap.
  • Utas UI diblokir menunggu operasi selesai.
  • Kebuntuan terjadi karena tidak satupun operasi yang dapat dilanjutkan.

Operasi lintas alur

Saat Anda perlu memperbarui kontrol UI dari utas latar belakang dalam operasi asinkron, gunakan teknik marshaling yang sesuai. Memahami perbedaan antara pendekatan pemblokiran dan non-pemblokiran sangat penting untuk aplikasi responsif.

.NET 9 memperkenalkan Control.InvokeAsync, yang menyediakan pendekatan marshaling yang ramah asinkron ke utas UI. Tidak seperti yang mengirim (memblokir utas panggilan), mem-posting (tanpa memblokir) ke antrean pesan utas UI. Untuk informasi selengkapnya tentang Control.InvokeAsync, lihat Cara melakukan panggilan thread-safe ke kontrol.

Keuntungan utama dari InvokeAsync:

  • Non-pemblokiran: Segera kembali, memungkinkan utas panggilan untuk melanjutkan.
  • Ramah digunakan asinkron: Mengembalikan Task yang bisa di-`await`.
  • Penyebaran pengecualian: Menyebarkan pengecualian dengan benar kembali ke kode panggilan.
  • Dukungan pembatalan: Mendukung CancellationToken pembatalan operasi.
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

Untuk operasi async yang benar-benar perlu berjalan pada 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

Petunjuk / Saran

.NET 9 menyertakan peringatan penganalisis (WFO2001) untuk membantu mendeteksi kapan metode asinkron salah diteruskan ke kelebihan beban sinkron .InvokeAsync Ini membantu mencegah perilaku "fire-and-forget".

Nota

Jika Anda menggunakan Visual Basic, cuplikan kode sebelumnya menggunakan metode ekstensi untuk mengonversi ValueTask ke Task. Kode metode ekstensi tersedia di GitHub.

Praktik terbaik

  • Gunakan asinkron/tunggu secara konsisten: Jangan mencampur pola asinkron dengan panggilan pemblokiran.
  • Menangani pengecualian: Selalu bungkus operasi asinkron di blok try-catch di async void penanganan aktivitas.
  • Berikan umpan balik pengguna: Perbarui UI untuk menampilkan kemajuan atau status operasi.
  • Menonaktifkan kontrol selama operasi: Mencegah pengguna memulai beberapa operasi.
  • Gunakan CancellationToken: Mendukung pembatalan operasi untuk tugas yang memakan waktu lama.
  • Pertimbangkan ConfigureAwait(false): Gunakan dalam kode pustaka untuk menghindari pengambilan konteks UI saat tidak diperlukan.