Практическое руководство. Осуществление потокобезопасных вызовов элементов управления Windows Forms.
При использовании многопоточности для улучшения производительности приложений Windows Forms во время вызова элементов управления необходимо соблюдать принципы безопасности потоков.
Доступ к элементам управления Windows Forms по сути не является потокобезопасным. При наличии двух или более потоков, контролирующих состояние элемента управления, последний может оказаться в несогласованном состоянии. Кроме того, могут возникнуть другие ошибки, связанные с потоками, включая состояния гонки и взаимоблокировки. Важно обеспечить потокобезопасный доступ к элементам управления.
Вызов элемента управления из каких-либо других потоков, за исключением потока, в котором был создан элемент управления, без использования метода Invoke является нарушением безопасности. Далее приведен пример вызова, выполняемого с нарушением принципов потокобезопасности.
' 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.";
}
.NET Framework помогает выявить обращения к элементам управления, не являющиеся потокобезопасными. При выполнении приложения в отладчике и попытке вызова элемента управления потоком, отличным от создавшего этот элемент управления, в отладчике создается исключение InvalidOperationException с сообщением "Обращение к элементу управления имя_элемента_управления из потока, не являющегося создателем элемента управления".
Это исключение всегда Происходит во время отладки, а в некоторых случаях во время выполнения. Исключение может появляться при отладке приложений, написанных с использованием .NET Framework, выпущенной ранее .NET Framework 2.0. Настоятельно рекомендуется решить эту проблему при ее обнаружении, однако предупреждение об ошибке можно отключить, задав значение false для свойства CheckForIllegalCrossThreadCalls. В этом случае элемент управления будет выполняться так же, как в Visual Studio .NET 2003 и .NET Framework 1.1.
Примечание
При использовании элементов управления ActiveX в форме может возникнуть несколько исключений InvalidOperationException в разных потоках, если выполнение осуществляется в отладчике.В таких случаях элемент управления ActiveX не поддерживает многопоточность.Дополнительные сведения об элементах ActiveX с Windows Forms см. в разделе Windows Forms и неуправляемые приложения.При использовании Visual Studio этого исключения можно избежать, отключив главный процесс Visual Studio.Дополнительные сведения см. в следующем разделе. Практическое руководство. Отключение главного процесса и Практическое руководство. Отключение главного процесса и Практическое руководство. Отключение главного процесса.
Осуществление потокобезопасных вызовов элементов управления Windows Forms.
Осуществление потокобезопасного вызова элемента управления Windows Forms
Опросите свойство InvokeRequired элемента управления.
Если InvokeRequired возвращает true, вызовите Invoke с делегатом, фактически вызывающим элемент управления.
Если InvokeRequired возвращает false, вызовите элемент управления напрямую.
В следующем примере кода потокобезопасный вызов реализуется в методе ThreadProcSafe, выполняемом фоновым потоком. Если InvokeRequired элемента управления TextBox возвращает true, метод создает ThreadProcSafe экземпляр SetTextCallback и передает его методу Invoke формы. При этом метод SetText вызывается в потоке, создавшем элемент управления TextBox, и в таком контексте потока свойство Text устанавливается непосредственно.
' 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;
}
}
Осуществление потокобезопасных вызовов с использованием компонента BackgroundWorker
Наиболее предпочтительным способом реализации многопоточности в приложении является использование компонента BackgroundWorker. Для реализации многопоточности компонент использует BackgroundWorker модель на основе событий. Фоновый поток выполняет обработчик событий DoWork, а поток, создающий элементы управления, выполняет обработчики событий ProgressChanged и RunWorkerCompleted. Элементы управления можно вызывать из обработчиков событий ProgressChanged и RunWorkerCompleted
Осуществление потокобезопасных вызовов с использованием компонента BackgroundWorker
Создайте метод для выполнения действий, которые должны выполняться в фоновом потоке. Не вызывайте элементы управления, созданные основным потоком в этом методе.
Создайте метод для составления отчетов по результатам работы фонового потока по завершении работы. В этом методе можно вызывать элементы управления, созданные основным потоком.
Свяжите метод, созданный в шаге 1, с событием DoWork экземпляра BackgroundWorker, и свяжите метод, созданный в шаге 2, с событием RunWorkerCompleted того же экземпляра.
Чтобы запустить фоновый поток, вызовите метод RunWorkerAsync экземпляра BackgroundWorker.
В следующем примере кода обработчик событий DoWork использует Sleep для моделирования заданий, на выполнение которых уходит определенное время. Обработчик событий не вызывает в форме элемент управления TextBox. Свойство Text элемента управления TextBox устанавливается непосредственно в обработчике событий RunWorkerCompleted.
' 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.";
}
Кроме того, отчет о ходе выполнения фоновой задачи можно составить с использованием события ProgressChanged. Пример, который содержит это событие, см. в разделе BackgroundWorker.
Пример
В следующем примере кода представлено полное приложение Windows Forms, состоящее из формы с тремя кнопками и одного текстового поля. Первая кнопка показывает доступ с нарушением принципов безопасности нескольких потоков, вторая кнопка показывает безопасный доступ с использованием Invoke, а третья — безопасный доступ с использованием BackgroundWorker.
Примечание
Инструкции по запуску этого примера см. в разделе Практическое руководство. Компиляция и выполнение откомпилированного примера кода формы Windows Forms с помощью Visual Studio.В примере необходимо использовать ссылки на сборки System.Drawing и 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());
}
Если запустить приложение и нажать кнопку Небезопасный вызов, в текстовом поле немедленно появится текст "Создано основным потоком". Через две секунды, если предпринимается попытка осуществления небезопасного вызова, отладчик Visual Studio показывает, что создается исключение. Отладчик останавливается в строке фонового потока, которая пыталась создать запись непосредственно в текстовом поле. Необходимо перезапустить приложение, чтобы протестировать две другие кнопки. Если нажать кнопку Безопасный вызов, в текстовом поле появляется текст "Создано основным потоком". Через две секунды в текстовом поле появляется надпись "Создано фоновым потоком (Invoke)", что означает, что был вызван метод Invoke. Если нажать кнопку Безопасный вызов BW, в текстовом поле появляется текст "Создано основным потоком". Через две секунды в текстовом поле появляется надпись "Создано основным потоком после завершения работы фонового потока", что означает, что был вызван обработчик события RunWorkerCompleted объекта BackgroundWorker.
Отказоустойчивость
Предупреждение
При использовании многопоточности любого рода код подвержен весьма серьезным и сложным ошибкам.Перед реализацией любого решения с многопоточностью изучите подробные сведения в разделе Рекомендации по работе с потоками.
См. также
Задачи
Практическое руководство. Фоновое выполнение операции
Практическое руководство. Реализация формы, в которой выполняется фоновая операция
Ссылки
Другие ресурсы
Разработка пользовательских элементов управления Windows Forms в .NET Framework