Gewusst wie: Erstellen von Thread sicheren Aufrufen von Windows Forms-SteuerelementenHow to: Make thread-safe calls to Windows Forms controls

Multithreading kann die Leistung von Windows Forms-apps verbessern, aber der Zugriff auf Windows Forms-Steuerelemente ist nicht grundsätzlich Thread sicher.Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn't inherently thread-safe. Multithreading kann Ihren Code für sehr ernste und komplexe Fehler verfügbar machen.Multithreading can expose your code to very serious and complex bugs. Zwei oder mehr Threads, die ein Steuerelement bearbeiten, können das Steuerelement in einen inkonsistenten Zustand versetzen und zu Racebedingungen, Deadlocks und einfriert bzw. hängen.Two or more threads manipulating a control can force the control into an inconsistent state and lead to race conditions, deadlocks, and freezes or hangs. Wenn Sie Multithreading in der APP implementieren, stellen Sie sicher, dass Thread übergreifende Steuerelemente auf Thread sichere Weise aufgerufen werden.If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. Weitere Informationen finden Sie unter bewährte Methoden für das verwaltete Threading.For more information, see Managed threading best practices.

Es gibt zwei Möglichkeiten, um ein Windows Forms-Steuerelement aus einem Thread, der dieses Steuerelement nicht erstellt hat, sicher aufzurufen.There are two ways to safely call a Windows Forms control from a thread that didn't create that control. Sie können die- System.Windows.Forms.Control.Invoke Methode verwenden, um einen im Haupt Thread erstellten Delegaten aufzurufen, der wiederum das-Steuerelement aufruft.You can use the System.Windows.Forms.Control.Invoke method to call a delegate created in the main thread, which in turn calls the control. Sie können auch einen implementieren System.ComponentModel.BackgroundWorker , bei dem ein ereignisgesteuerte Modell verwendet wird, um im Hintergrund Thread durchgeführte Arbeiten von der Berichterstellung für die Ergebnisse zu trennen.Or, you can implement a System.ComponentModel.BackgroundWorker, which uses an event-driven model to separate work done in the background thread from reporting on the results.

Unsichere Thread übergreifende AufrufeUnsafe cross-thread calls

Es ist unsicher, ein Steuerelement direkt von einem Thread aufzurufen, der ihn nicht erstellt hat.It's unsafe to call a control directly from a thread that didn't create it. Der folgende Code Ausschnitt veranschaulicht einen unsicheren-Befehl für das- System.Windows.Forms.TextBox Steuerelement.The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. Der Button1_Click -Ereignishandler erstellt einen neuen WriteTextUnsafe Thread, der die-Eigenschaft des Haupt Threads TextBox.Text direkt festlegt.The Button1_Click event handler creates a new WriteTextUnsafe thread, which sets the main thread's TextBox.Text property directly.

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

Der Visual Studio-Debugger erkennt diese unsicheren Thread Aufrufe, indem er InvalidOperationException mit der Meldung " Thread übergreifender Vorgang ungültig" aufruft. Das Steuerelement "", auf das von einem anderen Thread als dem erstellten Thread zugegriffen wird.The Visual Studio debugger detects these unsafe thread calls by raising an InvalidOperationException with the message, Cross-thread operation not valid. Control "" accessed from a thread other than the thread it was created on. Der InvalidOperationException tritt immer für unsichere Thread übergreifende Aufrufe während des Debuggens von Visual Studio auf und kann zur Laufzeit der app auftreten.The InvalidOperationException always occurs for unsafe cross-thread calls during Visual Studio debugging, and may occur at app runtime. Sie sollten das Problem beheben, aber Sie können die Ausnahme deaktivieren, indem Sie die- Control.CheckForIllegalCrossThreadCalls Eigenschaft auf festlegen false .You should fix the issue, but you can disable the exception by setting the Control.CheckForIllegalCrossThreadCalls property to false.

Sichere Thread übergreifende AufrufeSafe cross-thread calls

Die folgenden Codebeispiele veranschaulichen zwei Möglichkeiten, um ein Windows Forms-Steuerelement aus einem Thread, der ihn nicht erstellt hat, sicher aufzurufen:The following code examples demonstrate two ways to safely call a Windows Forms control from a thread that didn't create it:

  1. Die- System.Windows.Forms.Control.Invoke Methode, die einen Delegaten vom Haupt Thread aufruft, um das-Steuerelement aufzurufen.The System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. Eine System.ComponentModel.BackgroundWorker Komponente, die ein ereignisgesteuerte Modell bietet.A System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

In beiden Beispielen wird der Hintergrund Thread für eine Sekunde in den Ruhezustand versetzt, um die Arbeit in diesem Thread zu simulieren.In both examples, the background thread sleeps for one second to simulate work being done in that thread.

Sie können diese Beispiele als .NET Framework-Apps über die c#-oder Visual Basic-Befehlszeile erstellen und ausführen.You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. Weitere Informationen finden Sie unter Erstellen über die Befehlszeile mit csc.exe oder Build über die Befehlszeile (Visual Basic).For more information, see Command-line building with csc.exe or Build from the command line (Visual Basic).

Ab .net Core 3,0 können Sie die Beispiele auch als Windows .net Core-Apps aus einem Ordner erstellen und ausführen, der über eine .net Core Windows Forms <folder name> . csproj -Projektdatei verfügt.Starting with .NET Core 3.0, you can also build and run the examples as Windows .NET Core apps from a folder that has a .NET Core Windows Forms <folder name>.csproj project file.

Beispiel: Verwenden der Methode "aufrufen" mit einem DelegatenExample: Use the Invoke method with a delegate

Im folgenden Beispiel wird ein Muster zum Sicherstellen von Thread sicheren Aufrufen eines Windows Forms-Steuer Elements veranschaulicht.The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. Er fragt die System.Windows.Forms.Control.InvokeRequired -Eigenschaft ab, die die Erstellungsthread-ID des Steuer Elements mit der aufrufenden Thread-ID vergleicht.It queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control's creating thread ID to the calling thread ID. Wenn die Thread-IDs identisch sind, wird das-Steuerelement direkt aufgerufen.If the thread IDs are the same, it calls the control directly. Wenn sich die Thread-IDs unterscheiden, ruft Sie die- Control.Invoke Methode mit einem Delegaten aus dem Haupt Thread auf, der den eigentlichen Aufruf des Steuer Elements übernimmt.If the thread IDs are different, it calls the Control.Invoke method with a delegate from the main thread, which makes the actual call to the control.

SafeCallDelegateErmöglicht das Festlegen der TextBox -Eigenschaft des Steuer Elements Text .The SafeCallDelegate enables setting the TextBox control's Text property. Die WriteTextSafe Methoden Abfragen InvokeRequired .The WriteTextSafe method queries InvokeRequired. Wenn InvokeRequired zurückgibt true , WriteTextSafe übergibt das SafeCallDelegate an die- Invoke Methode, um den eigentlichen-Befehl an das-Steuerelement zu senden.If InvokeRequired returns true, WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. Wenn InvokeRequired zurückgibt false , WriteTextSafe legt den TextBox.Text direkt fest.If InvokeRequired returns false, WriteTextSafe sets the TextBox.Text directly. Der Button1_Click -Ereignishandler erstellt den neuen Thread und führt die- WriteTextSafe Methode aus.The Button1_Click event handler creates the new thread and runs the WriteTextSafe method.

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

Beispiel: Verwenden eines BackgroundWorker-Ereignis HandlersExample: Use a BackgroundWorker event handler

Eine einfache Möglichkeit zum Implementieren von Multithreading ist die- System.ComponentModel.BackgroundWorker Komponente, die ein Ereignis gesteuerter Modell verwendet.An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. Der Hintergrund Thread führt das- BackgroundWorker.DoWork Ereignis aus, das nicht mit dem Haupt Thread interagiert.The background thread runs the BackgroundWorker.DoWork event, which doesn't interact with the main thread. Der Haupt Thread führt den BackgroundWorker.ProgressChanged - BackgroundWorker.RunWorkerCompleted Ereignishandler und den-Ereignishandler aus, der die Steuerelemente des Haupt Threads abrufen kann.The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread's controls.

Erstellen Sie zum Ausführen eines Thread sicheren Aufrufes mithilfe von BackgroundWorker eine Methode im Hintergrund Thread, um die Arbeit durchzuführen, und binden Sie Sie an das DoWork Ereignis.To make a thread-safe call by using BackgroundWorker, create a method in the background thread to do the work, and bind it to the DoWork event. Erstellen Sie eine weitere Methode im Haupt Thread, um die Ergebnisse der Hintergrundarbeit zu melden und Sie an das-Ereignis oder das-Ereignis zu binden ProgressChanged RunWorkerCompleted .Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. Um den Hintergrund Thread zu starten, wird aufgerufen BackgroundWorker.RunWorkerAsync .To start the background thread, call BackgroundWorker.RunWorkerAsync.

Das Beispiel verwendet den RunWorkerCompleted -Ereignishandler, um die-Eigenschaft des-Steuer Elements festzulegen TextBox Text .The example uses the RunWorkerCompleted event handler to set the TextBox control's Text property. Ein Beispiel für die Verwendung des- ProgressChanged Ereignisses finden Sie unter BackgroundWorker .For an example using the ProgressChanged event, see 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

Siehe auchSee also