Gewusst wie: Threadsicheres Aufrufen von Windows Forms-Steuerelementen
Wenn Sie mithilfe von Multithreading die Leistung Ihrer Windows Forms-Anwendungen verbessern, müssen Sie die Steuerelemente threadsicher aufrufen.
Der Zugriff auf Windows Forms-Steuerelemente ist nicht grundsätzlich threadsicher. Wenn der Zustand eines Steuerelements von zwei oder mehr Threads geändert wird, können Sie für das Steuerelement einen inkonsistenten Zustand erzwingen. Andere Fehler in Verbindung mit Threads sind möglich, wie z. B. Racebedingungen und Deadlocks. Es ist wichtig, dass der Zugriff auf die Steuerelemente threadsicher erfolgt.
Es ist nicht sicher, ein Steuerelement ohne die Invoke-Methode aus einem anderen Thread aufzurufen als dem, mit dem das Steuerelement erstellt wurde. Das folgende Beispiel zeigt einen nicht threadsicheren Aufruf.
' This event handler creates a thread that calls a
' Windows Forms control in an unsafe way.
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' an unsafe call on the TextBox control.
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
End Sub
// This event handler creates a thread that calls a
// Windows Forms control in an unsafe way.
private void setTextUnsafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
// This method is executed on the worker thread and makes
// an unsafe call on the TextBox control.
private void ThreadProcUnsafe()
{
this.textBox1.Text = "This text was set unsafely.";
}
// This event handler creates a thread that calls a
// Windows Forms control in an unsafe way.
private:
void setTextUnsafeBtn_Click(Object^ sender, EventArgs^ e)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcUnsafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes
// an unsafe call on the TextBox control.
private:
void ThreadProcUnsafe()
{
this->textBox1->Text = "This text was set unsafely.";
}
Mit .NET Framework können Sie leicht erkennen, wenn der Zugriff auf die Steuerelemente nicht threadsicher ist. Wenn Sie die Anwendung im Debugger ausführen und ein Thread versucht, ein Steuerelement aufzurufen (mit Ausnahme des Threads, der dieses Steuerelement erstellt hat), löst der Debugger ein InvalidOperationException-Element mit der Meldung "Der Zugriff auf das Steuerelement Steuerelementname erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde" aus.
Diese Ausnahme tritt zuverlässig beim Debuggen und, unter gewissen Umständen, zur Laufzeit auf. Diese Ausnahme tritt möglicherweise auf, wenn Sie Anwendungen debuggen, die Sie mit einer früheren Version von .NET Framework als .NET Framework, Version 2.0 geschrieben haben. Es wird dringend empfohlen, dieses Problem zu beheben, wenn Sie es feststellen. Sie können es jedoch vermeiden, indem Sie die CheckForIllegalCrossThreadCalls-Eigenschaft auf false festlegen. Dadurch wird das Steuerelement wie unter Visual Studio .NET 2003 und .NET Framework 1.1 ausgeführt.
Tipp
Wenn Sie in einem Formular ActiveX-Steuerelemente verwenden und einen Debugger ausführen, erhalten Sie möglicherweise das threadübergreifende InvalidOperationException-Element. In diesem Fall unterstützt das ActiveX-Steuerelement kein Multithreading. Weitere Informationen über die Verwendung von ActiveX-Steuerelementen in Windows Forms finden Sie unter Windows Forms und nicht verwaltete Anwendungen. In Visual Studio können Sie das Auftreten dieser Ausnahme verhindern, indem Sie den Visual Studio-Hostprozess deaktivieren. Weitere Informationen finden Sie unter Gewusst wie: Deaktivieren des Hostprozesses und Gewusst wie: Deaktivieren des Hostprozesses und Gewusst wie: Deaktivieren des Hostprozesses und Gewusst wie: Deaktivieren des Hostprozesses.
Threadsicheres Aufrufen von Windows Forms-Steuerelementen
So rufen Sie ein Windows Forms-Steuerelement threadsicher auf
Fragen Sie die InvokeRequired-Eigenschaft des Steuerelements ab.
Wenn InvokeRequired true zurückgibt, rufen Sie Invoke mit einem Delegaten auf, der den eigentlichen Aufruf des Steuerelements übernimmt.
Wenn InvokeRequiredfalse zurückgibt, rufen Sie das Steuerelement direkt auf.
Im folgenden Codebeispiel wird ein threadsicherer Aufruf in der ThreadProcSafe-Methode implementiert, die vom Hintergrundthread ausgeführt wird. Wenn die InvokeRequired-Eigenschaft des TextBox-Steuerelements true zurückgibt, erstellt die ThreadProcSafe-Methode eine Instanz von SetTextCallback und übergibt diese an die Invoke-Methode des Formulars. Dies bewirkt, dass die SetText-Methode auf dem Thread aufgerufen wird, mit dem das TextBox-Steuerelement erstellt wurde. In diesem Threadzusammenhang wird die Text-Eigenschaft direkt festgelegt.
' This event handler creates a thread that calls a
' Windows Forms control in a thread-safe way.
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextSafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' a thread-safe call on the TextBox control.
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private void setTextSafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private:
void setTextSafeBtn_Click(Object^ sender, EventArgs^ e)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcSafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
private:
void ThreadProcSafe()
{
this->SetText("This text was set safely.");
}
' This method demonstrates a pattern for making thread-safe
' calls on a Windows Forms control.
'
' If the calling thread is different from the thread that
' created the TextBox control, this method creates a
' SetTextCallback and calls itself asynchronously using the
' Invoke method.
'
' If the calling thread is the same as the thread that created
' the TextBox control, the Text property is set directly.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextDelegate and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private:
void SetText(String^ text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this->textBox1->InvokeRequired)
{
SetTextDelegate^ d =
gcnew SetTextDelegate(this, &Form1::SetText);
this->Invoke(d, gcnew array<Object^> { text });
}
else
{
this->textBox1->Text = text;
}
}
Ausführen von threadsicheren Aufrufen mit BackgroundWorker
Zum Implementieren von Multithreading in der Anwendung wird vorzugsweise die BackgroundWorker-Komponente verwendet. Die BackgroundWorker-Komponente verwendet ein ereignisgesteuertes Modell für Multithreading. Der Hintergrundthread führt den DoWork-Ereignishandler aus, und der Thread, mit dem die Steuerelemente erstellt werden, führt den ProgressChanged-Ereignishandler und den RunWorkerCompleted-Ereignishandler aus. Sie können die Steuerelemente aus dem ProgressChanged-Ereignishandler und dem RunWorkerCompleted-Ereignishandler aufrufen.
So führen Sie threadsichere Aufrufe mit BackgroundWorker aus
Erstellen Sie eine Methode zur Ausführung der Aufgaben im Hintergrundthread. Rufen Sie in dieser Methode keine vom Hauptthread erstellten Steuerelemente auf.
Erstellen Sie eine Methode, mit der die Ergebnisse nach Beenden der Hintergrundverarbeitung protokolliert werden. In dieser Methode können Sie vom Hauptthread erstellte Steuerelemente aufrufen.
Binden Sie die in Schritt 1 erstellte Methode an das DoWork-Ereignis einer Instanz von BackgroundWorker und die in Schritt 2 erstellte Methode an das RunWorkerCompleted-Ereignis der gleichen Instanz.
Rufen Sie die RunWorkerAsync-Methode der BackgroundWorker-Instanz auf, um den Hintergrundthread zu starten.
Im folgenden Codebeispiel wird vom DoWork-Ereignishandler mit Sleep Arbeit simuliert, die einige Zeit in Anspruch nimmt. Das TextBox-Steuerelement des Formulars wird nicht aufgerufen. Die Text-Eigenschaft des TextBox-Steuerelements wird direkt im RunWorkerCompleted-Ereignishandler festgelegt.
' This BackgroundWorker is used to demonstrate the
' preferred way of performing asynchronous operations.
Private WithEvents backgroundWorker1 As BackgroundWorker
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private BackgroundWorker backgroundWorker1;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private:
BackgroundWorker^ backgroundWorker1;
' This event handler starts the form's
' BackgroundWorker by calling RunWorkerAsync.
'
' The Text property of the TextBox control is set
' when the BackgroundWorker raises the RunWorkerCompleted
' event.
Private Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub
' This event handler sets the Text property of the TextBox
' control. It is called on the thread that created the
' TextBox control, so the call is thread-safe.
'
' BackgroundWorker is the preferred way to perform asynchronous
' operations.
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
End Sub
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.textBox1.Text =
"This text was set safely by BackgroundWorker.";
}
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private:
void setTextBackgroundWorkerBtn_Click(Object^ sender, EventArgs^ e)
{
this->backgroundWorker1->RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
private:
void backgroundWorker1_RunWorkerCompleted(
Object^ sender,
RunWorkerCompletedEventArgs^ e)
{
this->textBox1->Text =
"This text was set safely by BackgroundWorker.";
}
Sie können den Fortschritt eines Hintergrundtasks mit dem ProgressChanged-Ereignis anzeigen. Ein Beispiel mit diesem Ereignis finden Sie unter BackgroundWorker.
Beispiel
Das folgende Codebeispiel ist eine vollständige Windows Forms-Anwendung, die aus einem Formular mit drei Schaltflächen und einem Textfeld besteht. Die erste Schaltfläche steht für den unsicheren threadübergreifenden Zugriff, die zweite Schaltfläche für den sicheren Zugriff mit Invoke und die dritte Schaltfläche für den sicheren Zugriff mit BackgroundWorker.
Tipp
Eine Anleitung zum Ausführen des Beispiels finden Sie unter Gewusst wie: Kompilieren und Ausführen eines vollständigen Windows Forms-Codebeispiels mit Visual Studio. Dieses Beispiel erfordert Verweise auf die Assemblys System.Drawing und System.Windows.Forms.
Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms
Public Class Form1
Inherits Form
' This delegate enables asynchronous calls for setting
' the text property on a TextBox control.
Delegate Sub SetTextCallback([text] As String)
' This thread is used to demonstrate both thread-safe and
' unsafe ways to call a Windows Forms control.
Private demoThread As Thread = Nothing
' This BackgroundWorker is used to demonstrate the
' preferred way of performing asynchronous operations.
Private WithEvents backgroundWorker1 As BackgroundWorker
Private textBox1 As TextBox
Private WithEvents setTextUnsafeBtn As Button
Private WithEvents setTextSafeBtn As Button
Private WithEvents setTextBackgroundWorkerBtn As Button
Private components As System.ComponentModel.IContainer = Nothing
Public Sub New()
InitializeComponent()
End Sub
Protected Overrides Sub Dispose(disposing As Boolean)
If disposing AndAlso (components IsNot Nothing) Then
components.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
' This event handler creates a thread that calls a
' Windows Forms control in an unsafe way.
Private Sub setTextUnsafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextUnsafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcUnsafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' an unsafe call on the TextBox control.
Private Sub ThreadProcUnsafe()
Me.textBox1.Text = "This text was set unsafely."
End Sub
' This event handler creates a thread that calls a
' Windows Forms control in a thread-safe way.
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextSafeBtn.Click
Me.demoThread = New Thread( _
New ThreadStart(AddressOf Me.ThreadProcSafe))
Me.demoThread.Start()
End Sub
' This method is executed on the worker thread and makes
' a thread-safe call on the TextBox control.
Private Sub ThreadProcSafe()
Me.SetText("This text was set safely.")
End Sub
' This method demonstrates a pattern for making thread-safe
' calls on a Windows Forms control.
'
' If the calling thread is different from the thread that
' created the TextBox control, this method creates a
' SetTextCallback and calls itself asynchronously using the
' Invoke method.
'
' If the calling thread is the same as the thread that created
' the TextBox control, the Text property is set directly.
Private Sub SetText(ByVal [text] As String)
' InvokeRequired required compares the thread ID of the
' calling thread to the thread ID of the creating thread.
' If these threads are different, it returns true.
If Me.textBox1.InvokeRequired Then
Dim d As New SetTextCallback(AddressOf SetText)
Me.Invoke(d, New Object() {[text]})
Else
Me.textBox1.Text = [text]
End If
End Sub
' This event handler starts the form's
' BackgroundWorker by calling RunWorkerAsync.
'
' The Text property of the TextBox control is set
' when the BackgroundWorker raises the RunWorkerCompleted
' event.
Private Sub setTextBackgroundWorkerBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextBackgroundWorkerBtn.Click
Me.backgroundWorker1.RunWorkerAsync()
End Sub
' This event handler sets the Text property of the TextBox
' control. It is called on the thread that created the
' TextBox control, so the call is thread-safe.
'
' BackgroundWorker is the preferred way to perform asynchronous
' operations.
Private Sub backgroundWorker1_RunWorkerCompleted( _
ByVal sender As Object, _
ByVal e As RunWorkerCompletedEventArgs) _
Handles backgroundWorker1.RunWorkerCompleted
Me.textBox1.Text = _
"This text was set safely by BackgroundWorker."
End Sub
#Region "Windows Form Designer generated code"
Private Sub InitializeComponent()
Me.textBox1 = New System.Windows.Forms.TextBox()
Me.setTextUnsafeBtn = New System.Windows.Forms.Button()
Me.setTextSafeBtn = New System.Windows.Forms.Button()
Me.setTextBackgroundWorkerBtn = New System.Windows.Forms.Button()
Me.backgroundWorker1 = New System.ComponentModel.BackgroundWorker()
Me.SuspendLayout()
'
' textBox1
'
Me.textBox1.Location = New System.Drawing.Point(12, 12)
Me.textBox1.Name = "textBox1"
Me.textBox1.Size = New System.Drawing.Size(240, 20)
Me.textBox1.TabIndex = 0
'
' setTextUnsafeBtn
'
Me.setTextUnsafeBtn.Location = New System.Drawing.Point(15, 55)
Me.setTextUnsafeBtn.Name = "setTextUnsafeBtn"
Me.setTextUnsafeBtn.TabIndex = 1
Me.setTextUnsafeBtn.Text = "Unsafe Call"
'
' setTextSafeBtn
'
Me.setTextSafeBtn.Location = New System.Drawing.Point(96, 55)
Me.setTextSafeBtn.Name = "setTextSafeBtn"
Me.setTextSafeBtn.TabIndex = 2
Me.setTextSafeBtn.Text = "Safe Call"
'
' setTextBackgroundWorkerBtn
'
Me.setTextBackgroundWorkerBtn.Location = New System.Drawing.Point(177, 55)
Me.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"
Me.setTextBackgroundWorkerBtn.TabIndex = 3
Me.setTextBackgroundWorkerBtn.Text = "Safe BW Call"
'
' backgroundWorker1
'
'
' Form1
'
Me.ClientSize = New System.Drawing.Size(268, 96)
Me.Controls.Add(setTextBackgroundWorkerBtn)
Me.Controls.Add(setTextSafeBtn)
Me.Controls.Add(setTextUnsafeBtn)
Me.Controls.Add(textBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
Me.PerformLayout()
End Sub 'InitializeComponent
#End Region
<STAThread()> _
Shared Sub Main()
Application.EnableVisualStyles()
Application.Run(New Form1())
End Sub
End Class
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace CrossThreadDemo
{
public class Form1 : Form
{
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void SetTextCallback(string text);
// This thread is used to demonstrate both thread-safe and
// unsafe ways to call a Windows Forms control.
private Thread demoThread = null;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private BackgroundWorker backgroundWorker1;
private TextBox textBox1;
private Button setTextUnsafeBtn;
private Button setTextSafeBtn;
private Button setTextBackgroundWorkerBtn;
private System.ComponentModel.IContainer components = null;
public Form1()
{
InitializeComponent();
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
// This event handler creates a thread that calls a
// Windows Forms control in an unsafe way.
private void setTextUnsafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
// This method is executed on the worker thread and makes
// an unsafe call on the TextBox control.
private void ThreadProcUnsafe()
{
this.textBox1.Text = "This text was set unsafely.";
}
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private void setTextSafeBtn_Click(
object sender,
EventArgs e)
{
this.demoThread =
new Thread(new ThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
private void ThreadProcSafe()
{
this.SetText("This text was set safely.");
}
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private void setTextBackgroundWorkerBtn_Click(
object sender,
EventArgs e)
{
this.backgroundWorker1.RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
private void backgroundWorker1_RunWorkerCompleted(
object sender,
RunWorkerCompletedEventArgs e)
{
this.textBox1.Text =
"This text was set safely by BackgroundWorker.";
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.setTextUnsafeBtn = new System.Windows.Forms.Button();
this.setTextSafeBtn = new System.Windows.Forms.Button();
this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();
this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 12);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(240, 20);
this.textBox1.TabIndex = 0;
//
// setTextUnsafeBtn
//
this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55);
this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";
this.setTextUnsafeBtn.TabIndex = 1;
this.setTextUnsafeBtn.Text = "Unsafe Call";
this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);
//
// setTextSafeBtn
//
this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55);
this.setTextSafeBtn.Name = "setTextSafeBtn";
this.setTextSafeBtn.TabIndex = 2;
this.setTextSafeBtn.Text = "Safe Call";
this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);
//
// setTextBackgroundWorkerBtn
//
this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55);
this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";
this.setTextBackgroundWorkerBtn.TabIndex = 3;
this.setTextBackgroundWorkerBtn.Text = "Safe BW Call";
this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);
//
// backgroundWorker1
//
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//
// Form1
//
this.ClientSize = new System.Drawing.Size(268, 96);
this.Controls.Add(this.setTextBackgroundWorkerBtn);
this.Controls.Add(this.setTextSafeBtn);
this.Controls.Add(this.setTextUnsafeBtn);
this.Controls.Add(this.textBox1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.Run(new Form1());
}
}
}
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>
using namespace System;
using namespace System::ComponentModel;
using namespace System::Threading;
using namespace System::Windows::Forms;
namespace CrossThreadDemo
{
public ref class Form1 : public Form
{
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
delegate void SetTextDelegate(String^ text);
// This thread is used to demonstrate both thread-safe and
// unsafe ways to call a Windows Forms control.
private:
Thread^ demoThread;
// This BackgroundWorker is used to demonstrate the
// preferred way of performing asynchronous operations.
private:
BackgroundWorker^ backgroundWorker1;
private:
TextBox^ textBox1;
private:
Button^ setTextUnsafeBtn;
private:
Button^ setTextSafeBtn;
private:
Button^ setTextBackgroundWorkerBtn;
private:
System::ComponentModel::IContainer^ components;
public:
Form1()
{
components = nullptr;
InitializeComponent();
}
protected:
~Form1()
{
if (components != nullptr)
{
delete components;
}
}
// This event handler creates a thread that calls a
// Windows Forms control in an unsafe way.
private:
void setTextUnsafeBtn_Click(Object^ sender, EventArgs^ e)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcUnsafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes
// an unsafe call on the TextBox control.
private:
void ThreadProcUnsafe()
{
this->textBox1->Text = "This text was set unsafely.";
}
// This event handler creates a thread that calls a
// Windows Forms control in a thread-safe way.
private:
void setTextSafeBtn_Click(Object^ sender, EventArgs^ e)
{
this->demoThread =
gcnew Thread(gcnew ThreadStart(this,&Form1::ThreadProcSafe));
this->demoThread->Start();
}
// This method is executed on the worker thread and makes
// a thread-safe call on the TextBox control.
private:
void ThreadProcSafe()
{
this->SetText("This text was set safely.");
}
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextDelegate and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private:
void SetText(String^ text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this->textBox1->InvokeRequired)
{
SetTextDelegate^ d =
gcnew SetTextDelegate(this, &Form1::SetText);
this->Invoke(d, gcnew array<Object^> { text });
}
else
{
this->textBox1->Text = text;
}
}
// This event handler starts the form's
// BackgroundWorker by calling RunWorkerAsync.
//
// The Text property of the TextBox control is set
// when the BackgroundWorker raises the RunWorkerCompleted
// event.
private:
void setTextBackgroundWorkerBtn_Click(Object^ sender, EventArgs^ e)
{
this->backgroundWorker1->RunWorkerAsync();
}
// This event handler sets the Text property of the TextBox
// control. It is called on the thread that created the
// TextBox control, so the call is thread-safe.
//
// BackgroundWorker is the preferred way to perform asynchronous
// operations.
private:
void backgroundWorker1_RunWorkerCompleted(
Object^ sender,
RunWorkerCompletedEventArgs^ e)
{
this->textBox1->Text =
"This text was set safely by BackgroundWorker.";
}
#pragma region Windows Form Designer generated code
private:
void InitializeComponent()
{
this->textBox1 = gcnew System::Windows::Forms::TextBox();
this->setTextUnsafeBtn = gcnew System::Windows::Forms::Button();
this->setTextSafeBtn = gcnew System::Windows::Forms::Button();
this->setTextBackgroundWorkerBtn =
gcnew System::Windows::Forms::Button();
this->backgroundWorker1 =
gcnew System::ComponentModel::BackgroundWorker();
this->SuspendLayout();
//
// textBox1
//
this->textBox1->Location = System::Drawing::Point(12, 12);
this->textBox1->Name = "textBox1";
this->textBox1->Size = System::Drawing::Size(240, 20);
this->textBox1->TabIndex = 0;
//
// setTextUnsafeBtn
//
this->setTextUnsafeBtn->Location = System::Drawing::Point(15, 55);
this->setTextUnsafeBtn->Name = "setTextUnsafeBtn";
this->setTextUnsafeBtn->TabIndex = 1;
this->setTextUnsafeBtn->Text = "Unsafe Call";
this->setTextUnsafeBtn->Click +=
gcnew System::EventHandler(
this,&Form1::setTextUnsafeBtn_Click);
//
// setTextSafeBtn
//
this->setTextSafeBtn->Location = System::Drawing::Point(96, 55);
this->setTextSafeBtn->Name = "setTextSafeBtn";
this->setTextSafeBtn->TabIndex = 2;
this->setTextSafeBtn->Text = "Safe Call";
this->setTextSafeBtn->Click +=
gcnew System::EventHandler(this,&Form1::setTextSafeBtn_Click);
//
// setTextBackgroundWorkerBtn
//
this->setTextBackgroundWorkerBtn->Location =
System::Drawing::Point(177, 55);
this->setTextBackgroundWorkerBtn->Name =
"setTextBackgroundWorkerBtn";
this->setTextBackgroundWorkerBtn->TabIndex = 3;
this->setTextBackgroundWorkerBtn->Text = "Safe BW Call";
this->setTextBackgroundWorkerBtn->Click +=
gcnew System::EventHandler(
this,&Form1::setTextBackgroundWorkerBtn_Click);
//
// backgroundWorker1
//
this->backgroundWorker1->RunWorkerCompleted +=
gcnew System::ComponentModel::RunWorkerCompletedEventHandler(
this,&Form1::backgroundWorker1_RunWorkerCompleted);
//
// Form1
//
this->ClientSize = System::Drawing::Size(268, 96);
this->Controls->Add(this->setTextBackgroundWorkerBtn);
this->Controls->Add(this->setTextSafeBtn);
this->Controls->Add(this->setTextUnsafeBtn);
this->Controls->Add(this->textBox1);
this->Name = "Form1";
this->Text = "Form1";
this->ResumeLayout(false);
this->PerformLayout();
}
#pragma endregion
};
}
[STAThread]
int main()
{
Application::EnableVisualStyles();
Application::Run(gcnew CrossThreadDemo::Form1());
}
Wenn Sie die Anwendung ausführen und auf die Schaltfläche Unsafe Call klicken, wird im Texfeld sofort "Written by the main thread" angezeigt. Zwei Sekunden später, wenn der unsichere Aufruf versucht wird, meldet der Visual Studio-Debugger eine Ausnahme. Der Debugger unterbricht die Ausführung an der Zeile im Hintergrundthread, mit der versucht wird, direkt in das Textfeld zu schreiben. Sie müssen die Anwendung neu starten, um die anderen zwei Schaltflächen zu testen. Wenn Sie auf die Schaltfläche Safe Call klicken, wird "Written by the main thread" im Textfeld angezeigt. Zwei Sekunden später wird im Textfeld mit "Written by the background thread (Invoke)" angegeben, dass die Invoke-Methode aufgerufen wurde. Wenn Sie auf die Schaltfläche Safe BW Call klicken, wird "Written by the main thread" im Textfeld angezeigt. Zwei Sekunden später wird im Textfeld mit "Written by the main thread after the background thread completed" angegeben, dass der Handler für das RunWorkerCompleted-Ereignis von BackgroundWorker aufgerufen wurde.
Robuste Programmierung
Warnung
Bei der Verwendung von Multithreading kann Ihr Code ernsten und komplexen Fehlern ausgesetzt sein. Es wird empfohlen, die Informationen unter Empfohlene Vorgehensweise für das verwaltete Threading zu lesen, bevor Sie eine Lösung implementieren, die Multithreading verwendet.
Siehe auch
Aufgaben
Gewusst wie: Ausführen eines Vorgangs im Hintergrund
Gewusst wie: Implementieren eines Formulars, das eine Hintergrundoperation verwendet
Referenz
Weitere Ressourcen
Entwickeln benutzerdefinierter Windows Forms-Steuerelemente mit .NET Framework