Bagikan melalui


Cara: Melakukan panggilan aman utas ke kontrol Formulir Windows

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 sangat 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. Anda dapat menggunakan System.Windows.Forms.Control.Invoke metode untuk memanggil delegasi yang dibuat di utas utama, yang pada gilirannya memanggil kontrol. Atau, Anda dapat menerapkan System.ComponentModel.BackgroundWorker, yang menggunakan model berbasis peristiwa untuk memisahkan pekerjaan yang dilakukan di utas latar belakang agar tidak melaporkan 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)
{
    thread2 = new Thread(new ThreadStart(WriteTextUnsafe));
    thread2.Start();
}
private void WriteTextUnsafe()
{
    textBox1.Text = "This text was set unsafely.";
}
Private Sub Button1_Click(ByVal sender As Object, e As EventArgs) Handles Button1.Click
    Thread2 = New Thread(New ThreadStart(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.

Anda dapat membangun dan menjalankan contoh ini sebagai aplikasi .NET Framework dari baris perintah C# atau Visual Basic. Untuk informasi selengkapnya, lihat Penyusunan baris perintah dengan csc.exe atau Build dari baris perintah (Visual Basic).

Dimulai dengan .NET Core 3.0, Anda juga dapat membuat dan menjalankan contoh sebagai aplikasi Windows .NET Core dari folder yang memiliki file proyek .NET Core Formulir Windows folder name.csproj>.<

Contoh: Gunakan metode Panggil dengan delegasi

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 ID utas sama, ID tersebut akan memanggil kontrol secara langsung. Jika ID utas berbeda, ia memanggil metode dengan delegasi dari utas Control.Invoke utama, yang melakukan panggilan aktual ke kontrol.

mengaktifkan SafeCallDelegate pengaturan TextBox properti kontrol Text . Metode WriteTextSafe mengkueri InvokeRequired. Jika InvokeRequired mengembalikan true, WriteTextSafe meneruskan SafeCallDelegate ke Invoke metode untuk melakukan panggilan aktual ke kontrol. Jika InvokeRequired mengembalikan false, WriteTextSafe mengatur secara TextBox.Text langsung. Penanganan Button1_Click aktivitas membuat utas WriteTextSafe baru dan menjalankan metode .

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class InvokeThreadSafeForm : Form
{
    private delegate void SafeCallDelegate(string text);
    private Button button1;
    private TextBox textBox1;
    private Thread thread2 = null;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        Application.EnableVisualStyles();
        Application.Run(new InvokeThreadSafeForm());
    }
    public InvokeThreadSafeForm()
    {
        button1 = new Button
        {
            Location = new Point(15, 55),
            Size = new Size(240, 20),
            Text = "Set text safely"
        };
        button1.Click += new EventHandler(Button1_Click);
        textBox1 = new TextBox
        {
            Location = new Point(15, 15),
            Size = new Size(240, 20)
        };
        Controls.Add(button1);
        Controls.Add(textBox1);
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        thread2 = new Thread(new ThreadStart(SetText));
        thread2.Start();
        Thread.Sleep(1000);
    }

    private void WriteTextSafe(string text)
    {
        if (textBox1.InvokeRequired)
        {
            var d = new SafeCallDelegate(WriteTextSafe);
            textBox1.Invoke(d, new object[] { text });
        }
        else
        {
            textBox1.Text = text;
        }
    }

    private void SetText()
    {
        WriteTextSafe("This text was set safely.");
    }
}
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms

Public Class InvokeThreadSafeForm : Inherits Form

    Public Shared Sub Main()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.EnableVisualStyles()
        Dim frm As New InvokeThreadSafeForm()
        Application.Run(frm)
    End Sub

    Dim WithEvents Button1 As Button
    Dim TextBox1 As TextBox
    Dim Thread2 as Thread = Nothing

    Delegate Sub SafeCallDelegate(text As String)

    Private Sub New()
        Button1 = New Button()
        With Button1
            .Location = New Point(15, 55)
            .Size = New Size(240, 20)
            .Text = "Set text safely"
        End With
        TextBox1 = New TextBox()
        With TextBox1
            .Location = New Point(15, 15)
            .Size = New Size(240, 20)
        End With
        Controls.Add(Button1)
        Controls.Add(TextBox1)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Thread2 = New Thread(New ThreadStart(AddressOf SetText))
        Thread2.Start()
        Thread.Sleep(1000)
    End Sub

    Private Sub WriteTextSafe(text As String)
        If TextBox1.InvokeRequired Then
            Dim d As New SafeCallDelegate(AddressOf SetText)
            TextBox1.Invoke(d, New Object() {text})
        Else
            TextBox1.Text = text
        End If
    End Sub

    Private Sub SetText()
        WriteTextSafe("This text was set safely.")
    End Sub
End Class

Contoh: Menggunakan penanganan aktivitas BackgroundWorker

Cara mudah untuk menerapkan multithreading adalah dengan System.ComponentModel.BackgroundWorker komponen, yang menggunakan model berbasis peristiwa. Utas latar belakang menjalankan 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, buat metode di utas latar belakang untuk melakukan pekerjaan, dan ikat ke DoWork peristiwa. Buat metode lain di utas utama untuk melaporkan hasil pekerjaan latar belakang, dan mengikatnya ke ProgressChanged atau RunWorkerCompleted peristiwa. Untuk memulai utas latar belakang, panggil BackgroundWorker.RunWorkerAsync.

Contoh menggunakan penanganan RunWorkerCompleted aktivitas untuk mengatur TextBox properti kontrol Text . Untuk contoh menggunakan peristiwa, ProgressChanged lihat BackgroundWorker.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

public class BackgroundWorkerForm : Form
{
    private BackgroundWorker backgroundWorker1;
    private Button button1;
    private TextBox textBox1;

    [STAThread]
    static void Main()
    {
        Application.SetCompatibleTextRenderingDefault(false);
        Application.EnableVisualStyles();
        Application.Run(new BackgroundWorkerForm());
    }
    public BackgroundWorkerForm()
    {
        backgroundWorker1 = new BackgroundWorker();
        backgroundWorker1.DoWork += new DoWorkEventHandler(BackgroundWorker1_DoWork);
        backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorker1_RunWorkerCompleted);
        button1 = new Button
        {
            Location = new Point(15, 55),
            Size = new Size(240, 20),
            Text = "Set text safely with BackgroundWorker"
        };
        button1.Click += new EventHandler(Button1_Click);
        textBox1 = new TextBox
        {
            Location = new Point(15, 15),
            Size = new Size(240, 20)
        };
        Controls.Add(button1);
        Controls.Add(textBox1);
    }
    private void Button1_Click(object sender, EventArgs e)
    {
        backgroundWorker1.RunWorkerAsync();
    }

    private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // Sleep 2 seconds to emulate getting data.
        Thread.Sleep(2000);
        e.Result = "This text was set safely by BackgroundWorker.";
    }

    private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        textBox1.Text = e.Result.ToString();
    }
}
Imports System.ComponentModel
Imports System.Drawing
Imports System.Threading
Imports System.Windows.Forms

Public Class BackgroundWorkerForm : Inherits Form

    Public Shared Sub Main()
        Application.SetCompatibleTextRenderingDefault(False)
        Application.EnableVisualStyles()
        Dim frm As New BackgroundWorkerForm()
        Application.Run(frm)
    End Sub

    Dim WithEvents BackgroundWorker1 As BackgroundWorker
    Dim WithEvents Button1 As Button
    Dim TextBox1 As TextBox

    Private Sub New()
        BackgroundWorker1 = New BackgroundWorker()
        Button1 = New Button()
        With Button1
            .Text = "Set text safely with BackgroundWorker"
            .Location = New Point(15, 55)
            .Size = New Size(240, 20)
        End With
        TextBox1 = New TextBox()
        With TextBox1
            .Location = New Point(15, 15)
            .Size = New Size(240, 20)
        End With
        Controls.Add(Button1)
        Controls.Add(TextBox1)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        BackgroundWorker1.RunWorkerAsync()
    End Sub

    Private Sub BackgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs) _
     Handles BackgroundWorker1.DoWork
        ' Sleep 2 seconds to emulate getting data.
        Thread.Sleep(2000)
        e.Result = "This text was set safely by BackgroundWorker."
    End Sub

    Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) _
     Handles BackgroundWorker1.RunWorkerCompleted
        textBox1.Text = e.Result.ToString()
    End Sub
End Class

Baca juga