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.
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
.
Panggilan lintas utas yang aman
Contoh kode berikut menunjukkan dua cara untuk memanggil kontrol Formulir Windows dengan aman dari utas yang tidak membuatnya:
- Metode ini System.Windows.Forms.Control.Invoke , yang memanggil delegasi dari utas utama untuk memanggil kontrol.
- 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
.NET Desktop feedback