zoukankan      html  css  js  c++  java
  • 对 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

  • 相关阅读:
    VBScript 语法函数(ScriptControl 控件)
    VB 读和写配置文件
    【转】VB控件和对象:ScriptControl 控件
    VB ADODB中的RecordSet.Open打开记录的两个参数adOpenKeyset、adLockBatchOptimistic的详细说明
    JavaScript的replace方法与正则表达式结合应用讲解
    jQuery DatePicker
    PHP date与gmdate的区别及如何修改PHP的默认时区
    jQuery获取Select选择的Text和 Value(转)
    JavaScript字符串函数大全
    CONFLICTING TYPES的原因
  • 原文地址:https://www.cnblogs.com/shenfx318/p/645257.html
Copyright © 2011-2022 走看看