如何:对 Windows 窗体控件进行线程安全调用

来源:互联网 发布:星球基地mac 编辑:程序博客网 时间:2024/05/16 06:29

转载连接: http://blog.csdn.net/eyuanatqqdotcom/article/details/7560787


如何:对 Windows 窗体控件进行线程安全调用

使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。

示例

访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。

.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”

此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。

Note注意

可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。

下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的 Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。

 代码:

Form1:

[csharp] view plain copy
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9. using System.Threading;  
  10.   
  11. namespace CrossThreadDemo  
  12. {  
  13.     public partial class Form1 : Form  
  14.     {  
  15.         //该代理允许异步调用来设置TextBox控件的Text属性  
  16.         delegate void SetTextCallback(string text);  
  17.   
  18.         //该线程用于演示线程安全和非线程安全的调用Window窗体控件  
  19.         private Thread demoThread = null;  
  20.   
  21.         public Form1()  
  22.         {  
  23.             InitializeComponent();  
  24.         }  
  25.   
  26.         private void Form1_Load(object sender, EventArgs e)  
  27.         {  
  28.   
  29.         }  
  30.  
  31.         #region 非线程安全  
  32.         //该事件处理过程(按钮点击事件处理过程)创建了一线程用来一非线程安全的方式调用Window窗体控件  
  33.         /// <summary>  
  34.         /// 非线程安全,按钮点击,事件处理  
  35.         /// </summary>  
  36.         /// <param name="sender"></param>  
  37.         /// <param name="e"></param>  
  38.         private void setTextUnsafeBtn_Click(object sender, EventArgs e)  
  39.         {  
  40.             this.demoThread =  
  41.                 new Thread(new ThreadStart(this.ThreadProcUnsafe));  
  42.   
  43.             this.demoThread.Start();  
  44.   
  45.         }  
  46.         //该方法在一个工人(次)线程上执行,并且非线程安全地调用了TextBox控件  
  47.         private void ThreadProcUnsafe()  
  48.         {  
  49.             this.textBox1.Text = "该文本被非线程安全的形式设置";  
  50.         }  
  51.         #endregion  
  52.  
  53.         #region 线程安全  
  54.         //该事件处理过程创建了一线程以线程安全的方式调用Windows窗体控件  
  55.         /// <summary>  
  56.         /// 线程安全,按钮点击,事件处理  
  57.         /// </summary>  
  58.         /// <param name="sender"></param>  
  59.         /// <param name="e"></param>  
  60.         private void setTextSafeBtn_Click(object sender, EventArgs e)  
  61.         {  
  62.             this.demoThread =  
  63.                 new Thread(new ThreadStart(this.ThreadProcSafe));  
  64.   
  65.             this.demoThread.Start();  
  66.   
  67.         }  
  68.         //该方法在一个次线程上执行,并且线程安全地调用了TextBox控件  
  69.         private void ThreadProcSafe()  
  70.         {  
  71.             this.SetText("该文本被以线程安全的方式设置");  
  72.         }  
  73.         //如果调用Windows窗体控件(此处为TextBox)的线程不是创建该控件的线程,  
  74.         //该方法创建一个SetTextCallback(委托)并且通过该委托实例的Invoke方法异步地调用自己  
  75.         //如果调用控件的线程和创建该控件的线程是同一个线程,那么就直接设置TextBox的Text属性  
  76.         private void SetText(string text)  
  77.         {  
  78.             //InvokeRequired属性需要比较调用线程和创建线程的线程ID,如果俩线程ID不同,则返回true  
  79.             if (this.textBox1.InvokeRequired)//调用和创建该控件的线程是不同的线程,必须调用Invoke方法  
  80.             {  
  81.                 //将设置TextBox控件Text属性的方法本身作为构建委托对象的参数  
  82.                 //创建该方法的委托实例  
  83.                 SetTextCallback d = new SetTextCallback(SetText);  
  84.                 //调用该委托实例,并传递参数,参数为object类型,使用this调用Invoke(this为当前窗体,是创建该窗体控件的线程)  
  85.                 this.Invoke(d, new object[] { text });//this指定创建该控件的线程来Invoke(调用)  
  86.             }  
  87.             else//调用与创建该控件的线程是同一个线程  
  88.             {  
  89.                 this.textBox1.Text = text;  
  90.             }  
  91.         }  
  92.         #endregion  
  93.  
  94.         #region 后台Worker  
  95.   
  96.         //该事件处理过程(按钮点击),通过调用窗体中的BackgroundWorker组件实例的RunWorkerAsync方法  
  97.         //开始执行后台操作  
  98.         //当BackgroundWorker(后台线程)触发RunWorkerCompleted(工作线程完成)时,TextBox控件的Text属性被设置  
  99.         /// <summary>  
  100.         /// 后台Worker,按钮点击,事件处理  
  101.         /// </summary>  
  102.         /// <param name="sender"></param>  
  103.         /// <param name="e"></param>  
  104.         private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)  
  105.         {  
  106.             //调用窗体中的BackgroundWorker组件实例的RunWorkerAsync方法  
  107.             this.backgroundWorker1.RunWorkerAsync();  
  108.   
  109.         }  
  110.         //该事件处理过程(后台线程的工作线程完成事件)设置TextBox控件的Text属性。  
  111.         //它由创建TextBox控件的线程调用,因此是线程安全的。  
  112.         //BackgroundWorker(后台工作线程)是处理异步操作的最好的方式  
  113.         private void backgroundWorker1_RunWorkerCompleted(  
  114.             object sender,  
  115.             RunWorkerCompletedEventArgs e)  
  116.         {  
  117.             this.textBox1.Text =  
  118.                 "该文本由后台线程安全地设置";  
  119.         }  
  120.         #endregion  
  121.     }  
  122. }  


Form1.Designer:

[csharp] view plain copy
  1. namespace CrossThreadDemo  
  2. {  
  3.     partial class Form1  
  4.     {  
  5.         /// <summary>  
  6.         /// 必需的设计器变量。  
  7.         /// </summary>  
  8.         private System.ComponentModel.IContainer components = null;  
  9.   
  10.         /// <summary>  
  11.         /// 清理所有正在使用的资源。  
  12.         /// </summary>  
  13.         /// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>  
  14.         protected override void Dispose(bool disposing)  
  15.         {  
  16.             if (disposing && (components != null))  
  17.             {  
  18.                 components.Dispose();  
  19.             }  
  20.             base.Dispose(disposing);  
  21.         }  
  22.  
  23.         #region Windows 窗体设计器生成的代码  
  24.   
  25.         /// <summary>  
  26.         /// 设计器支持所需的方法 - 不要  
  27.         /// 使用代码编辑器修改此方法的内容。  
  28.         /// </summary>  
  29.         private void InitializeComponent()  
  30.         {  
  31.             this.textBox1 = new System.Windows.Forms.TextBox();  
  32.             this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker();  
  33.             this.setTextUnsafeBtn = new System.Windows.Forms.Button();  
  34.             this.setTextSafeBtn = new System.Windows.Forms.Button();  
  35.             this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button();  
  36.             this.SuspendLayout();  
  37.             //   
  38.             // textBox1  
  39.             //   
  40.             this.textBox1.Location = new System.Drawing.Point(18, 13);  
  41.             this.textBox1.Name = "textBox1";  
  42.             this.textBox1.Size = new System.Drawing.Size(251, 21);  
  43.             this.textBox1.TabIndex = 0;  
  44.             //   
  45.             // backgroundWorker1  
  46.             //   
  47.             this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);  
  48.             //   
  49.             // setTextUnsafeBtn  
  50.             //   
  51.             this.setTextUnsafeBtn.Location = new System.Drawing.Point(16, 54);  
  52.             this.setTextUnsafeBtn.Name = "setTextUnsafeBtn";  
  53.             this.setTextUnsafeBtn.Size = new System.Drawing.Size(75, 23);  
  54.             this.setTextUnsafeBtn.TabIndex = 1;  
  55.             this.setTextUnsafeBtn.Text = "非线程安全";  
  56.             this.setTextUnsafeBtn.UseVisualStyleBackColor = true;  
  57.             this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click);  
  58.             //   
  59.             // setTextSafeBtn  
  60.             //   
  61.             this.setTextSafeBtn.Location = new System.Drawing.Point(106, 54);  
  62.             this.setTextSafeBtn.Name = "setTextSafeBtn";  
  63.             this.setTextSafeBtn.Size = new System.Drawing.Size(75, 23);  
  64.             this.setTextSafeBtn.TabIndex = 2;  
  65.             this.setTextSafeBtn.Text = "线程安全";  
  66.             this.setTextSafeBtn.UseVisualStyleBackColor = true;  
  67.             this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click);  
  68.             //   
  69.             // setTextBackgroundWorkerBtn  
  70.             //   
  71.             this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(198, 54);  
  72.             this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn";  
  73.             this.setTextBackgroundWorkerBtn.Size = new System.Drawing.Size(75, 23);  
  74.             this.setTextBackgroundWorkerBtn.TabIndex = 3;  
  75.             this.setTextBackgroundWorkerBtn.Text = "后台Worker";  
  76.             this.setTextBackgroundWorkerBtn.UseVisualStyleBackColor = true;  
  77.             this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click);  
  78.             //   
  79.             // Form1  
  80.             //   
  81.             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);  
  82.             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;  
  83.             this.ClientSize = new System.Drawing.Size(290, 90);  
  84.             this.Controls.Add(this.setTextBackgroundWorkerBtn);  
  85.             this.Controls.Add(this.setTextSafeBtn);  
  86.             this.Controls.Add(this.setTextUnsafeBtn);  
  87.             this.Controls.Add(this.textBox1);  
  88.             this.Name = "Form1";  
  89.             this.Text = "Form1";  
  90.             this.Load += new System.EventHandler(this.Form1_Load);  
  91.             this.ResumeLayout(false);  
  92.             this.PerformLayout();  
  93.   
  94.         }  
  95.  
  96.         #endregion  
  97.   
  98.         private System.Windows.Forms.TextBox textBox1;  
  99.         private System.ComponentModel.BackgroundWorker backgroundWorker1;  
  100.         private System.Windows.Forms.Button setTextUnsafeBtn;  
  101.         private System.Windows.Forms.Button setTextSafeBtn;  
  102.         private System.Windows.Forms.Button setTextBackgroundWorkerBtn;  
  103.     }  
  104. }  


步骤总结:

对 Windows 窗体控件的非线程安全调用

对 Windows 窗体控件的非线程安全调用方式是从辅助线程直接调用。调用应用程序时,调试器会引发一个InvalidOperationException,警告对控件的调用不是线程安全的。

(代码略,上边代码的非线程安全部分)

对 Windows 窗体控件的线程安全调用

对 Windows 窗体控件进行线程安全调用

  1. 查询控件的 InvokeRequired 属性。

  2. 如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。

  3. 如果 InvokeRequired 返回 false,则直接调用控件。

在下面的代码示例中,此逻辑是在一个称为 SetText 的实用工具方法中实现的。名为 SetTextDelegate 的委托类型封装SetText 方法。TextBox 控件的 InvokeRequired 返回true 时,SetText 方法创建 SetTextDelegate 的一个实例,并调用窗体的Invoke 方法。这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置Text属性。

(代码略,上边代码的线程安全部分)

使用 BackgroundWorker 进行的线程安全调用

在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。BackgroundWorker 组件使用事件驱动模型实现多线程。辅助线程运行 DoWork 事件处理程序,创建控件的线程运行 ProgressChanged 和 RunWorkerCompleted事件处理程序。注意不要从 DoWork 事件处理程序调用您的任何控件。

下面的代码示例不异步执行任何工作,因此没有 DoWork 事件处理程序的实现。TextBox 控件的Text 属性在RunWorkerCompleted 事件处理程序中直接设置。

(代码略,上边代码的后台工作线程部分)

 

备注(不敢谎称译注):

该文章原文本是简体中文,但作为程序员,大家都明白,文字说明并不能让你了解多少内容,实例代码才是你能够学习到知识的地方,这也就是很多人的博客里简单的说上几句介绍,然后说“看代码”。对于读者来说,光看文字说明是看不懂的,对于作者来说,光写文字说明也是写不出想法的。只有把代码和说明放在一块,才方便读写,所有多数人在代码里写很多注释来说明,而不是在代码外写大段的注释。

这篇文字原文是中文,但是代码的注释却都是英文,因此我尝试着把英文注释翻译为中文,在转载和翻译上犹豫了一会,最后无耻的选择了翻译。。。

我没有什么本事,去翻译一篇好文章,只能自己硬着头皮读一些不得不读的E文文章或者代码,这次的情况也是这样,在自己将就着读下来以后,想把它汉化后备份下来,等到哪天需要的时候,再翻出来参考下。但放在自己的机子上并不是一个好的方法,东西乱糟糟的,过一段时间就找不到了,放在网上,自己容易找到,还可以让百度、谷歌帮忙查找。

如果你链接到了这篇文章,然后找到了你想要的东西,那我很高兴,如果耽误了你的时间,我只能说句对不起了。

QQ:373048914,希望和众多的菜鸟共同进步。


原创粉丝点击