Vorgehensweise: Threadsichere Aufrufe von Windows Forms-SteuerelementenHow to: Make thread-safe calls to Windows Forms controls

Multithreading kann die Leistung von Windows Forms-Anwendungen verbessern, aber den Zugriff auf Windows Forms-Steuerelemente nicht grundsätzlich threadsicher ist.Multithreading can improve the performance of Windows Forms apps, but access to Windows Forms controls isn't inherently thread-safe. Multithreading kann Ihr Code sehr ernsthaften und komplexen Fehlern verfügbar machen.Multithreading can expose your code to very serious and complex bugs. Zwei oder mehr Threads, die ein Steuerelement bearbeiten können erzwingen, dass das Steuerelement in einem inkonsistenten Zustand und dazu führen, dass Racebedingungen, Deadlocks und Einfrieren oder hängt.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 implementieren multithreading in Ihrer app werden Sie sicher, dass threadübergreifenden-Steuerelemente auf threadsichere Weise aufrufen.If you implement multithreading in your app, be sure to call cross-thread controls in a thread-safe way. Weitere Informationen finden Sie unter Managed threading best Practices.For more information, see Managed threading best practices.

Es gibt zwei Möglichkeiten, ein Windows Forms-Steuerelement sicher von einem anderen Thread aufrufen können, die das Steuerelement erstellt haben.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 zum Aufrufen eines Delegaten erstellt im Hauptthread, das 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. Oder Implementieren einer System.ComponentModel.BackgroundWorker, die ein ereignisgesteuertes Modell verwendet, um Arbeit, die von der berichterstellung für die Ergebnisse im Hintergrundthread 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 anderen Thread aufrufen, die sie erstellt haben.It's unsafe to call a control directly from a thread that didn't create it. Der folgende Codeausschnitt veranschaulicht einen unsichere Aufruf der System.Windows.Forms.TextBox Steuerelement.The following code snippet illustrates an unsafe call to the System.Windows.Forms.TextBox control. Die Button1_Click -Ereignishandler erstellt ein neues WriteTextUnsafe Thread, der des Hauptthread des legt TextBox.Text -Eigenschaft direkt.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

Visual Studio-Debugger erkennt diese threadaufrufe unsichere durch Auslösen einer InvalidOperationException mit der Meldung threadübergreifender Vorgang ungültig. Steuerelement "" zugegriffen werden, von einem anderen Thread als dem Thread, der es erstellt wurde.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. Die InvalidOperationException immer tritt bei unsicheren threadübergreifende Aufrufe, während des Debuggens von Visual Studio, und zur app-Laufzeit 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, durch Festlegen der Control.CheckForIllegalCrossThreadCalls Eigenschaft 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, ein Windows Forms-Steuerelement sicher von einem anderen Thread aufrufen können, die sie erstellt haben: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, über den Hauptthread aufruft, das Steuerelement aufzurufen.The System.Windows.Forms.Control.Invoke method, which calls a delegate from the main thread to call the control.
  2. Ein System.ComponentModel.BackgroundWorker -Komponente, die ein ereignisgesteuertes Modell bietet.A System.ComponentModel.BackgroundWorker component, which offers an event-driven model.

In beiden Beispielen funktioniert der Background Thread ruht für eine Sekunde, um zu simulieren, in diesem Thread ausgeführt wird.In both examples, the background thread sleeps for one second to simulate work being done in that thread.

Können Sie erstellen und Ausführen dieser Beispiele als .NET Framework-apps aus dem C# oder Visual Basic über die Befehlszeile.You can build and run these examples as .NET Framework apps from the C# or Visual Basic command line. Weitere Informationen finden Sie unter 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, Sie können auch erstellen und Ausführen der Beispiele als Windows .NET Core-apps aus einem Ordner, die eine .NET Core Windows Forms <Ordnername > .csproj Projektdatei.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 Sie die Invoke-Methode mit einem DelegatenExample: Use the Invoke method with a delegate

Das folgende Beispiel zeigt ein Muster zum Sicherstellen der threadsichere Aufrufe an eine Windows Forms-Steuerelement.The following example demonstrates a pattern for ensuring thread-safe calls to a Windows Forms control. Fragt die System.Windows.Forms.Control.InvokeRequired -Eigenschaft, die das Steuerelement vergleicht die Thread-ID an den aufrufenden Thread-ID erstellenIt queries the System.Windows.Forms.Control.InvokeRequired property, which compares the control's creating thread ID to the calling thread ID. Wenn der Thread-IDs identisch sind, werden das Steuerelement direkt aufruft.If the thread IDs are the same, it calls the control directly. Wenn der Thread-IDs unterscheiden, ruft es die Control.Invoke Methode mit einem Delegaten über den Hauptthread, der den eigentlichen Aufruf an das Steuerelement ü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.

Die SafeCallDelegate Einstellung ermöglicht die TextBox des Steuerelements Text Eigenschaft.The SafeCallDelegate enables setting the TextBox control's Text property. Die WriteTextSafe Methodenabfragen InvokeRequired.The WriteTextSafe method queries InvokeRequired. Wenn InvokeRequired gibt true, WriteTextSafe übergibt die SafeCallDelegate auf die Invoke Methode, um den eigentlichen Aufruf des Steuerelements.If InvokeRequired returns true, WriteTextSafe passes the SafeCallDelegate to the Invoke method to make the actual call to the control. Wenn InvokeRequired gibt false, WriteTextSafe legt die TextBox.Text direkt.If InvokeRequired returns false, WriteTextSafe sets the TextBox.Text directly. Die Button1_Click -Ereignishandler erstellt dann den neuen Thread und führt die WriteTextSafe Methode.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);
            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)
            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 Sie einen BackgroundWorker-EreignishandlerExample: Use a BackgroundWorker event handler

Eine einfache Möglichkeit zum Implementieren von multithreading ist mit der System.ComponentModel.BackgroundWorker Komponente, die ein ereignisgesteuertes Modell verwendet.An easy way to implement multithreading is with the System.ComponentModel.BackgroundWorker component, which uses an event-driven model. Der Hintergrundthread führt den BackgroundWorker.DoWork -Ereignis, das den Hauptthread interagieren nicht.The background thread runs the BackgroundWorker.DoWork event, which doesn't interact with the main thread. Im Hauptthread ausgeführt wird. die BackgroundWorker.ProgressChanged und BackgroundWorker.RunWorkerCompleted Ereignishandler, die Steuerelemente für den Hauptthread aufgerufen werden können.The main thread runs the BackgroundWorker.ProgressChanged and BackgroundWorker.RunWorkerCompleted event handlers, which can call the main thread's controls.

Um einen threadsicheren Aufruf durch den Einsatz, BackgroundWorker, erstellen Sie eine Methode im Hintergrundthread für die Arbeit, und binden Sie es an der 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 andere Methode im Hauptthread melden die Ergebnisse der Hintergrundarbeit und binden Sie es an der ProgressChanged oder RunWorkerCompleted Ereignis.Create another method in the main thread to report the results of the background work, and bind it to the ProgressChanged or RunWorkerCompleted event. Rufen Sie zum Starten des Hintergrundthreads BackgroundWorker.RunWorkerAsync.To start the background thread, call BackgroundWorker.RunWorkerAsync.

Im Beispiel wird die RunWorkerCompleted -Ereignishandler zum Festlegen der TextBox des Steuerelements Text Eigenschaft.The example uses the RunWorkerCompleted event handler to set the TextBox control's Text property. Ein Beispiel mit der ProgressChanged Ereignis 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