<转载>跨线程进行Windows窗体控件的访问

访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
         .NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”
         可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。但是这样做会使程序出现预料之外的问题,如果程序的各线程之间没有互相争抢控件资源的情况,那么可以考虑采用这个办法。例如:
public Form1()
         ...{
             InitializeComponent();
             CheckForIllegalCrossThreadCalls = false;
         }

下面分别演示一下线程安全与非线程安全访问的实现
这里演示了两种线程安全的控件调用方法:
一种是利用delegate接口以及Invoke方法(黄色背景)
一种是使用BackgroundWorker方法(绿色背景)
目的都是通过调用创建该控件的线程,来对控件进行操作。
namespace CrossThreadDemo
...{
     public class Form1 : Form
     ...{
         // 这个 delegate 用来实现对TextBox控件Text属性的异步操作
         delegate void SetTextCallback(string text);
         // 创建用来调用控件的线程对象
         private Thread demoThread = null;
         // 利用backgroundworker进行线程安全调用
         private BackgroundWorker backgroundWorker1;
         private TextBox textBox1;
         private Button setTextUnsafeBtn;
         private Button setTextSafeBtn;
         private Button setTextBackgroundWorkerBtn;
         private System.ComponentModel.IContainer components = null;
         // 按钮事件,创建非线程安全调用的线程
         private void setTextUnsafeBtn_Click(
             object sender,
             EventArgs e)
         ...{
             this.demoThread =
                 new Thread(new ThreadStart(this.ThreadProcUnsafe));
             this.demoThread.Start();
         }
         // 采用非线程安全调用
         private void ThreadProcUnsafe()
         ...{
             this.textBox1.Text = "This text was set unsafely.";
         }
         // 按钮事件,创建线程安全调用的线程
         private void setTextSafeBtn_Click(
             object sender,
             EventArgs e)
         ...{
             this.demoThread =
                 new Thread(new ThreadStart(this.ThreadProcSafe));
             this.demoThread.Start();
         }
         // 调用worker线程,采用线程安全调用
         private void ThreadProcSafe()
         ...{
             this.SetText("This text was set safely.");
         }
         // 使用线程安全方法对窗体控件进行操作
         // 首先查询控件的InvokeRequired属性,以此来判断是不是正在从创建这个控件的线程访问该控件
         // 如果不是正在从创建这个控件的线程访问该控件,该方法将创建 SetTextDelegate 的一个实例,
     // 并调用窗体的 Invoke 方法,这使得 SetText 方法被创建 TextBox 控件的线程调用,而且在此线程上下文中将直接设置 Text 属性
         //
         // 如果是从创建该控件的线程访问它,则直接对其进行操作
         private void SetText(string text)
         ...{
             // InvokeRequired 比较线程ID以及创建控件的线程ID,不同则返回true
             if (this.textBox1.InvokeRequired)
             ...{   
                 SetTextCallback d = new SetTextCallback(SetText);
                 this.Invoke(d, new object[] ...{ text });
             }
             else
             ...{
                 this.textBox1.Text = text;
             }
         }
         // 该事件通过调用RunWorkerAsync方法启动BackgroundWorker
         // TextBox的Text属性将在 BackgroundWorker 发生 RunWorkerCompleted 事件之后设置好
         private void setTextBackgroundWorkerBtn_Click(
             object sender,
             EventArgs e)
         ...{
             this.backgroundWorker1.RunWorkerAsync();
         }       
         // 通过调用创建控件的线程来更改控件属性,所以是线程安全的
         // BackgroundWorker 是首选的异步控件操作方法
         private void backgroundWorker1_RunWorkerCompleted(
             object sender,
             RunWorkerCompletedEventArgs e)
         ...{
             this.textBox1.Text =
                 "This text was set safely by BackgroundWorker.";
         }
     }

posted @ 2011-05-18 19:51  孤獨龍  阅读(364)  评论(0编辑  收藏  举报