Bagikan melalui


Cara melakukan panggilan aman utas ke kontrol (Formulir Windows .NET)

Multithreading dapat meningkatkan performa aplikasi Formulir Windows, tetapi akses ke kontrol Formulir Windows secara inheren tidak aman untuk alur. Multithreading dapat mengekspos kode Anda ke bug yang serius dan kompleks. Dua utas atau lebih yang memanipulasi kontrol dapat memaksa kontrol menjadi keadaan yang tidak konsisten dan menyebabkan kondisi balapan, kebuntuan, dan beku atau menggantung. Jika Anda menerapkan multithreading di aplikasi, pastikan untuk memanggil kontrol lintas utas dengan cara yang aman untuk utas. Untuk informasi selengkapnya, lihat Praktik terbaik utas terkelola.

Penting

Dokumentasi Panduan Desktop untuk .NET 7 dan .NET 6 sedang dibangun.

Ada dua cara untuk memanggil kontrol Formulir Windows dengan aman dari utas yang tidak membuat kontrol tersebut. System.Windows.Forms.Control.Invoke Gunakan metode untuk memanggil delegasi yang dibuat di utas utama, yang pada gilirannya memanggil kontrol. Atau, terapkan System.ComponentModel.BackgroundWorker, yang menggunakan model berbasis peristiwa untuk memisahkan pekerjaan yang dilakukan di utas latar belakang dari pelaporan pada hasilnya.

Panggilan lintas alur yang tidak aman

Tidak aman untuk memanggil kontrol langsung dari utas yang tidak membuatnya. Cuplikan kode berikut mengilustrasikan panggilan tidak aman ke System.Windows.Forms.TextBox kontrol. Penanganan Button1_Click aktivitas membuat utas baru WriteTextUnsafe , yang mengatur properti utas TextBox.Text utama secara langsung.

private void button1_Click(object sender, EventArgs e)
{
    var thread2 = new System.Threading.Thread(WriteTextUnsafe);
    thread2.Start();
}

private void WriteTextUnsafe() =>
    textBox1.Text = "This text was set unsafely.";
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim thread2 As New System.Threading.Thread(AddressOf WriteTextUnsafe)
    thread2.Start()
End Sub

Private Sub WriteTextUnsafe()
    TextBox1.Text = "This text was set unsafely."
End Sub

Debugger Visual Studio mendeteksi panggilan utas yang tidak aman ini dengan menaikkan InvalidOperationException dengan pesan, operasi Lintas alur tidak valid. Kontrol yang diakses dari utas selain utas tempat alur dibuat.InvalidOperationException selalu terjadi untuk panggilan lintas utas yang tidak aman selama penelusuran kesalahan Visual Studio, dan dapat terjadi pada runtime aplikasi. Anda harus memperbaiki masalah, tetapi Anda dapat menonaktifkan pengecualian dengan mengatur Control.CheckForIllegalCrossThreadCalls properti ke false.

Brankas panggilan lintas alur

Contoh kode berikut menunjukkan dua cara untuk memanggil kontrol Formulir Windows dengan aman dari utas yang tidak membuatnya:

  1. Metode ini System.Windows.Forms.Control.Invoke , yang memanggil delegasi dari utas utama untuk memanggil kontrol.
  2. Komponen System.ComponentModel.BackgroundWorker , yang menawarkan model berbasis peristiwa.

Dalam kedua contoh, utas latar belakang tidur selama satu detik untuk mensimulasikan pekerjaan yang dilakukan di utas tersebut.

Contoh: Gunakan metode Panggil

Contoh berikut menunjukkan pola untuk memastikan panggilan aman utas ke kontrol Formulir Windows. Ini mengkueri System.Windows.Forms.Control.InvokeRequired properti , yang membandingkan ID alur pembuatan kontrol dengan ID utas panggilan. Jika berbeda, Anda harus memanggil metode .Control.Invoke

WriteTextSafe mengaktifkan pengaturan TextBox properti kontrol Text ke nilai baru. Metode mengkueri InvokeRequired. Jika InvokeRequired mengembalikan true, WriteTextSafe secara rekursif memanggil dirinya sendiri, meneruskan metode sebagai delegasi ke Invoke metode . Jika InvokeRequired mengembalikan false, WriteTextSafe mengatur secara TextBox.Text langsung. Penanganan Button1_Click aktivitas membuat utas WriteTextSafe baru dan menjalankan metode .

private void button1_Click(object sender, EventArgs e)
{
    var threadParameters = new System.Threading.ThreadStart(delegate { WriteTextSafe("This text was set safely."); });
    var thread2 = new System.Threading.Thread(threadParameters);
    thread2.Start();
}

public void WriteTextSafe(string text)
{
    if (textBox1.InvokeRequired)
    {
        // Call this same method but append THREAD2 to the text
        Action safeWrite = delegate { WriteTextSafe($"{text} (THREAD2)"); };
        textBox1.Invoke(safeWrite);
    }
    else
        textBox1.Text = text;
}
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    Dim threadParameters As New System.Threading.ThreadStart(Sub()
                                                                 WriteTextSafe("This text was set safely.")
                                                             End Sub)

    Dim thread2 As New System.Threading.Thread(threadParameters)
    thread2.Start()

End Sub

Private Sub WriteTextSafe(text As String)

    If (TextBox1.InvokeRequired) Then

        TextBox1.Invoke(Sub()
                            WriteTextSafe($"{text} (THREAD2)")
                        End Sub)

    Else
        TextBox1.Text = text
    End If

End Sub

Contoh: Menggunakan BackgroundWorker

Cara mudah untuk menerapkan multithreading adalah dengan System.ComponentModel.BackgroundWorker komponen, yang menggunakan model berbasis peristiwa. Utas latar belakang meningkatkan peristiwa, yang tidak berinteraksi dengan utas BackgroundWorker.DoWork utama. Utas BackgroundWorker.ProgressChanged utama menjalankan penanganan aktivitas dan BackgroundWorker.RunWorkerCompleted , yang dapat memanggil kontrol utas utama.

Untuk melakukan panggilan aman utas dengan menggunakan BackgroundWorker, tangani DoWork peristiwa. Ada dua peristiwa yang digunakan pekerja latar belakang untuk melaporkan status: ProgressChanged dan RunWorkerCompleted. Peristiwa ProgressChanged ini digunakan untuk mengomunikasikan pembaruan status ke utas utama, dan RunWorkerCompleted peristiwa tersebut digunakan untuk memberi sinyal bahwa pekerja latar belakang telah menyelesaikan pekerjaannya. Untuk memulai utas latar belakang, panggil BackgroundWorker.RunWorkerAsync.

Contoh dihitung dari 0 hingga 10 dalam DoWork peristiwa, dijeda selama satu detik di antara hitungan. Ini menggunakan penanganan ProgressChanged aktivitas untuk melaporkan nomor kembali ke utas utama dan mengatur TextBox properti kontrol Text . ProgressChanged Agar acara berfungsi, BackgroundWorker.WorkerReportsProgress properti harus diatur ke true.

private void button1_Click(object sender, EventArgs e)
{
    if (!backgroundWorker1.IsBusy)
        backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    int counter = 0;
    int max = 10;

    while (counter <= max)
    {
        backgroundWorker1.ReportProgress(0, counter.ToString());
        System.Threading.Thread.Sleep(1000);
        counter++;
    }
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) =>
    textBox1.Text = (string)e.UserState;
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    If (Not BackgroundWorker1.IsBusy) Then
        BackgroundWorker1.RunWorkerAsync()
    End If

End Sub

Private Sub BackgroundWorker1_DoWork(sender As Object, e As ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

    Dim counter = 0
    Dim max = 10

    While counter <= max

        BackgroundWorker1.ReportProgress(0, counter.ToString())
        System.Threading.Thread.Sleep(1000)

        counter += 1

    End While

End Sub

Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged
    TextBox1.Text = e.UserState
End Sub