Vorgehensweise: Thread sichere Aufrufe an Windows Forms Steuerelemente erstellenHow 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 System.ComponentModel.BackgroundWorkerimplementieren, 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 WriteTextUnsafe neuen Thread, der die- TextBox.Text Eigenschaft des Haupt Threads 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, InvalidOperationException indem er 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 Control.CheckForIllegalCrossThreadCalls die- falseEigenschaft auf festlegen.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# Befehlszeile oder Visual Basic 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 Erstellen ü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 einen .net Core-Windows Forms <Ordnername > 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 Aufruf Methode 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 Control.Invoke Sie die-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.

Ermöglicht das Festlegen der TextBox - Text Eigenschaft des Steuer Elements. SafeCallDelegateThe SafeCallDelegate enables setting the TextBox control's Text property. Die WriteTextSafe Methoden Abfragen InvokeRequired.The WriteTextSafe method queries InvokeRequired. Wenn InvokeRequired zurück truegibt ,WriteTextSafe übergibt dasSafeCallDelegate an dieInvoke -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ück falsegibt ,WriteTextSafe legt denTextBox.Text direkt fest.If InvokeRequired returns false, WriteTextSafe sets the TextBox.Text directly. Der Button1_Click -Ereignishandler erstellt den neuen Thread und führt WriteTextSafe die-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 BackgroundWorkervon 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 ProgressChanged - RunWorkerCompleted Ereignis oder das-Ereignis zu binden.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, BackgroundWorker.RunWorkerAsyncwird aufgerufen.To start the background thread, call BackgroundWorker.RunWorkerAsync.

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