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

 

 

今天在编写一个windows应用程序的时候碰到了一个小问题,程序需求是这样的,创建多个线程调用执行某个方法,Windows Form中有个Progress Bar控件用于显示已经执行完毕的进程数,即当所有的线程都运行完毕后,Progress Bar的进度也到头了。先给出初步的实现方式:

 

 1           private   const   int  MAXTHREAD  =   100 ;  // 最大线程数
 2           private   int  n  =   0 , count  =   0 ;  // 实际线程数、已结束的线程数
 3 
 4           private   void  StartTest()
 5          {
 6              n  =   int .Parse(txtThreadCount.Text);  // 线程数
 7              progressBar1.Maximum  =  n  *   10 ;  // 设置ProgressBar的最大值
 8 
 9              Thread thread  =   null ;
10              List < Thread >  threads  =   new  List < Thread > (MAXTHREAD);
11              ReadTableTest t  =   new  ReadTableTest(tableName, fileds); 
12              t.ThreadExitEvent  +=   new  ThreadExit(OnThreadExit);  // 线程执行完毕后通知主界面
13 
14               try
15              {
16                   // 创建线程
17                   for  ( int  i  =   0 ; i  <  n; i ++ )
18                  {
19                      thread  =   new  Thread( new  ThreadStart(t.Run));
20                      threads.Add(thread);
21                  }
22 
23                   // 开始调用接口
24                   foreach  (Thread tt  in  threads)
25                  {
26                      tt.Start();
27                  }
28              }
29               catch  (Exception ex)
30              {
31                  MessageBox.Show(ex.Message);
32              }
33          }
34 
35           // 线程执行完毕后回调
36           public   void  OnThreadExit()
37          {
38              count ++ ;
39               this .progressBar1.Value  =  count  *   10 ;  // 设置ProgressBar的进度
40 
41               // 判断是否全部进程已结束
42               if  (count  ==  n)
43              {
44                  MessageBox.Show( " 所有线程已执行完毕! " );
45                  ClearState();
46              }
47          }

当调试执行这段程序时在this.progressBar1.Value = count * 10;处,报出了InvalidOperationException Cross-thread operation not valid异常,在网上搜索一番后,发现产生该问题的原因如下。

问题原因

由于 Windows 窗体控件本质上不是线程安全的。因此如果有两个或多个线程适度操作某一控件的状态(set value),则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用和死锁的情况。于是在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException

解决方案

对于该异常的解决方案有两种,一种是关闭该异常检测的方式来避免异常的出现,经过测试发现此种方法虽然避免了异常的抛出,但是并不能保证程序运行结果的正确性,对于此例来说,经常是全部线程结束时,进度条的显示还未到头呢。下面再来看看更加优雅的解决方案,即通过保证线程的安全性来避免该异常,先来看看两种方案的代码。

方案1 

1           public  Form1()
2          {
3              InitializeComponent();
4              Control.CheckForIllegalCrossThreadCalls  =   false ;
5          }

 

说明

关闭CheckForIllegalCrossThreadCalls,这是Control class上的一个static property,默认值为flase,目的在于开关是否对Handle的可能存在的不一致存取的监测;且该项设置是具有Application scope的。

方案2 

 1  // 未给出代码的部分没有变化
 2           private   delegate   void  SafeSetProgressBarValue( int  v);
 3 
 4          // 线程执行完毕后回调
 5           public   void  OnThreadExit()
 6          {
 7              count ++ ;
 8              OnSafeSetValue(count  *   10 );  //使用线程安全的代码 设置ProgressBar的进度
 9 
10               // 判断是否全部进程已结束
11               if  (count  ==  n)
12              {
13                  MessageBox.Show( " 所有线程已执行完毕! " );
14                  ClearState();
15              }
16          }
17 
18           ///   <summary>
19           ///  线程安全的修改ProgressBarValue方式。
20           ///   </summary>
21           ///   <param name="va"></param>
22           private   void  OnSafeSetValue( int  va)
23          {
24              
25               if  ( this .progressBar1.InvokeRequired)
26              {
27                  SafeSetProgressBarValue call  =   delegate ( int  v) {  this .progressBar1.Value  =  v; };
28   
29                   this .progressBar1.Invoke(call,va);
30              }
31               else
32                   this .progressBar1.Value  =  va;
33          }

 说明

Windows 窗体中的控件被绑定到特定的线程,不具备线程安全性。因此,如果从另一个线程调用控件的方法,那么必须使用控件的一个 Invoke 方法来将调用封送到适当的线程。该属性可用于确定是否必须调用 Invoke 方法,当不知道什么线程拥有控件时这很有用。控件上有四种方法可以安全地从任何线程进行调用:Invoke、BeginInvoke、EndInvoke 和 CreateGraphics。对于所有其他方法调用,当从另一个线程进行调用时,应使用这些 Invoke 方法之一。

Control.InvokeRequired 属性

获取一个值,该值指示调用方在对控件进行方法调用时是否必须调用 Invoke 方法,因为调用方位于创建控件所在的线程以外的线程中。

属性值

如果控件的 Handle 是在与调用线程不同的线程上创建的(说明您必须通过 Invoke 方法对控件进行调用),则为 true;否则为 false。

更多资料:

http://msdn2.microsoft.com/zh-cn/library/ms171728(VS.80).aspx

http://msdn2.microsoft.com/zh-cn/library/system.windows.forms.control.invokerequired(VS.80).aspx

http://blog.csdn.net/joem/archive/2006/12/18/1448198.aspx

 

 

 

原文链接:http://www.cnblogs.com/shenfx318/archive/2007/02/08/645257.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值