浅谈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)引发的线程安全问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。