浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题
如何:对Windows窗体控件进行线程安全调用
访问Windows窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能会出现其他与线程相关的Bug,例如争用情况和死锁。确保以线程安全方式访问控件非常重要。
在未使用Invoke方法的情况下,从不是创建某个控件的线程的其他线程调用该控件是不安全的。以下非线程安全的调用的示例。
//Thiseventhandlercreatesathreadthatcallsa
//WindowsFormscontrolinanunsafeway.
privatevoidsetTextUnsafeBtn_Click(
objectsender,
EventArgse)
{
this.demoThread=
newThread(newThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
//Thismethodisexecutedontheworkerthreadandmakes
//anunsafecallontheTextBoxcontrol.
privatevoidThreadProcUnsafe()
{
this.textBox1.Text="Thistextwassetunsafely.";
}
.NETFramework可帮助您检测以非线程安全方式访问控件这一问题。在调试器中运行应用程序时,如果一个不是创建某个控件的线程的其他线程调用该控件,则调试器会引发一个InvalidOperationException,并显示以下消息:“从不是创建控件控件名称的线程访问它。”
此异常在调试期间和运行时的某些情况下可靠地发生。在调试以.NETFramework2.0版之前的.NETFramework编写的应用程序时,可能会出现此异常。我们强烈建议您在发现此问题时进行修复,但您可以通过将CheckForIllegalCrossThreadCalls属性设置为false来禁用它。(不推荐)
对Windows窗体控件进行线程安全调用
查询控件的InvokeRequired属性。
如果InvokeRequired返回true,则使用实际调用控件的委托来调用Invoke。
如果InvokeRequired返回false,则直接调用控件。
在下面的代码示例中,将在由后台线程执行的ThreadProcSafe方法中实现线程安全调用。如果TextBox控件的InvokeRequired返回true,则ThreadProcSafe方法会创建SetTextCallback的一个实例,并将该实例传递给窗体的Invoke方法。这使得SetText方法被创建TextBox控件的线程调用,而且在此线程上下文中将直接设置Text属性。
//Thiseventhandlercreatesathreadthatcallsa
//WindowsFormscontrolinathread-safeway.
privatevoidsetTextSafeBtn_Click(
objectsender,
EventArgse)
{
this.demoThread=
newThread(newThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
//Thismethodisexecutedontheworkerthreadandmakes
//athread-safecallontheTextBoxcontrol.
privatevoidThreadProcSafe()
{
this.SetText("Thistextwassetsafely.");
}
usingSystem;
usingSystem.ComponentModel;
usingSystem.Threading;
usingSystem.Windows.Forms;
namespaceCrossThreadDemo
{
publicclassForm1:Form
{
//Thisdelegateenablesasynchronouscallsforsetting
//thetextpropertyonaTextBoxcontrol.
delegatevoidSetTextCallback(stringtext);
//Thisthreadisusedtodemonstrateboththread-safeand
//unsafewaystocallaWindowsFormscontrol.
privateThreaddemoThread=null;
//ThisBackgroundWorkerisusedtodemonstratethe
//preferredwayofperformingasynchronousoperations.
privateBackgroundWorkerbackgroundWorker1;
privateTextBoxtextBox1;
privateButtonsetTextUnsafeBtn;
privateButtonsetTextSafeBtn;
privateButtonsetTextBackgroundWorkerBtn;
privateSystem.ComponentModel.IContainercomponents=null;
publicForm1()
{
InitializeComponent();
}
protectedoverridevoidDispose(booldisposing)
{
if(disposing&&(components!=null))
{
components.Dispose();
}
base.Dispose(disposing);
}
//Thiseventhandlercreatesathreadthatcallsa
//WindowsFormscontrolinanunsafeway.
privatevoidsetTextUnsafeBtn_Click(
objectsender,
EventArgse)
{
this.demoThread=
newThread(newThreadStart(this.ThreadProcUnsafe));
this.demoThread.Start();
}
//Thismethodisexecutedontheworkerthreadandmakes
//anunsafecallontheTextBoxcontrol.
privatevoidThreadProcUnsafe()
{
this.textBox1.Text="Thistextwassetunsafely.";
}
//Thiseventhandlercreatesathreadthatcallsa
//WindowsFormscontrolinathread-safeway.
privatevoidsetTextSafeBtn_Click(
objectsender,
EventArgse)
{
this.demoThread=
newThread(newThreadStart(this.ThreadProcSafe));
this.demoThread.Start();
}
//Thismethodisexecutedontheworkerthreadandmakes
//athread-safecallontheTextBoxcontrol.
privatevoidThreadProcSafe()
{
this.SetText("Thistextwassetsafely.");
}
//Thismethoddemonstratesapatternformakingthread-safe
//callsonaWindowsFormscontrol.
//
//Ifthecallingthreadisdifferentfromthethreadthat
//createdtheTextBoxcontrol,thismethodcreatesa
//SetTextCallbackandcallsitselfasynchronouslyusingthe
//Invokemethod.
//
//Ifthecallingthreadisthesameasthethreadthatcreated
//theTextBoxcontrol,theTextpropertyissetdirectly.
privatevoidSetText(stringtext)
{
//InvokeRequiredrequiredcomparesthethreadIDofthe
//callingthreadtothethreadIDofthecreatingthread.
//Ifthesethreadsaredifferent,itreturnstrue.
if(this.textBox1.InvokeRequired)
{
SetTextCallbackd=newSetTextCallback(SetText);
this.Invoke(d,newobject[]{text});
}
else
{
this.textBox1.Text=text;
}
}
//Thiseventhandlerstartstheform's
//BackgroundWorkerbycallingRunWorkerAsync.
//
//TheTextpropertyoftheTextBoxcontrolisset
//whentheBackgroundWorkerraisestheRunWorkerCompleted
//event.
privatevoidsetTextBackgroundWorkerBtn_Click(
objectsender,
EventArgse)
{
this.backgroundWorker1.RunWorkerAsync();
}
//ThiseventhandlersetstheTextpropertyoftheTextBox
//control.Itiscalledonthethreadthatcreatedthe
//TextBoxcontrol,sothecallisthread-safe.
//
//BackgroundWorkeristhepreferredwaytoperformasynchronous
//operations.
privatevoidbackgroundWorker1_RunWorkerCompleted(
objectsender,
RunWorkerCompletedEventArgse)
{
this.textBox1.Text=
"ThistextwassetsafelybyBackgroundWorker.";
}
#regionWindowsFormDesignergeneratedcode
privatevoidInitializeComponent()
{
this.textBox1=newSystem.Windows.Forms.TextBox();
this.setTextUnsafeBtn=newSystem.Windows.Forms.Button();
this.setTextSafeBtn=newSystem.Windows.Forms.Button();
this.setTextBackgroundWorkerBtn=newSystem.Windows.Forms.Button();
this.backgroundWorker1=newSystem.ComponentModel.BackgroundWorker();
this.SuspendLayout();
//
//textBox1
//
this.textBox1.Location=newSystem.Drawing.Point(12,12);
this.textBox1.Name="textBox1";
this.textBox1.Size=newSystem.Drawing.Size(240,20);
this.textBox1.TabIndex=0;
//
//setTextUnsafeBtn
//
this.setTextUnsafeBtn.Location=newSystem.Drawing.Point(15,55);
this.setTextUnsafeBtn.Name="setTextUnsafeBtn";
this.setTextUnsafeBtn.TabIndex=1;
this.setTextUnsafeBtn.Text="UnsafeCall";
this.setTextUnsafeBtn.Click+=newSystem.EventHandler(this.setTextUnsafeBtn_Click);
//
//setTextSafeBtn
//
this.setTextSafeBtn.Location=newSystem.Drawing.Point(96,55);
this.setTextSafeBtn.Name="setTextSafeBtn";
this.setTextSafeBtn.TabIndex=2;
this.setTextSafeBtn.Text="SafeCall";
this.setTextSafeBtn.Click+=newSystem.EventHandler(this.setTextSafeBtn_Click);
//
//setTextBackgroundWorkerBtn
//
this.setTextBackgroundWorkerBtn.Location=newSystem.Drawing.Point(177,55);
this.setTextBackgroundWorkerBtn.Name="setTextBackgroundWorkerBtn";
this.setTextBackgroundWorkerBtn.TabIndex=3;
this.setTextBackgroundWorkerBtn.Text="SafeBWCall";
this.setTextBackgroundWorkerBtn.Click+=newSystem.EventHandler(this.setTextBackgroundWorkerBtn_Click);
//
//backgroundWorker1
//
this.backgroundWorker1.RunWorkerCompleted+=newSystem.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//
//Form1
//
this.ClientSize=newSystem.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]
staticvoidMain()
{
Application.EnableVisualStyles();
Application.Run(newForm1());
}
}
}
以上这篇浅谈C#跨线程调用窗体控件(比如TextBox)引发的线程安全问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。