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

原创 2007年10月02日 16:21:00

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

private void StartTest()
{
    n = int.Parse(txtThreadCount.Text); //线程数
    progressBar1.Maximum = n * 10; //设置ProgressBar的最大值

    Thread thread = null;
    List<Thread> threads = new List<Thread>(MAXTHREAD);
    ReadTableTest t = new ReadTableTest(tableName, fileds);
    t.ThreadExitEvent += new ThreadExit(OnThreadExit); //线程执行完毕后通知主界面

    try
    {
        //创建线程
        for (int i = 0; i < n; i++)
        {
            thread = new Thread(new ThreadStart(t.Run));
            threads.Add(thread);
        }

        //开始调用接口
        foreach (Thread tt in threads)
        {
            tt.Start();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

//线程执行完毕后回调
public void OnThreadExit()
{
    count++;
    this.progressBar1.Value = count * 10; //设置ProgressBar的进度

    //判断是否全部进程已结束
    if (count == n)
    {
        MessageBox.Show("所有线程已执行完毕!");
        ClearState();
    }
}

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

问题原因

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


解决方案

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

方案1 
public Form1()
{
    InitializeComponent();
    Control.CheckForIllegalCrossThreadCalls = false;
}

说明

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


方案2 
//未给出代码的部分没有变化
private delegate void SafeSetProgressBarValue(int v);

//线程执行完毕后回调
public void OnThreadExit()
{
    count++;
    OnSafeSetValue(count * 10); //使用线程安全的代码设置ProgressBar的进度

    //判断是否全部进程已结束
    if (count == n)
    {
        MessageBox.Show("所有线程已执行完毕!");
        ClearState();
    }
}

/// <summary>
/// 线程安全的修改ProgressBarValue方式。
/// </summary>
/// <param name="va"></param>
private void OnSafeSetValue(int va)
{

    if (this.progressBar1.InvokeRequired)
    {
        SafeSetProgressBarValue call = delegate(int v) { this.progressBar1.Value = v; };

        this.progressBar1.Invoke(call, va);
    }
    else
        this.progressBar1.Value = va;
}

说明

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  

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

参考自:http://msdn.microsoft.com/ZH-CN/library/SYSTEM.WINDOWS.FORMS.CONTROL.INVOKE.aspx 如果使用多线程来提高 Win...
  • xiaobai1593
  • xiaobai1593
  • 2012年02月28日 16:54
  • 3370

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

...
  • Chendy
  • Chendy
  • 2007年10月31日 11:54
  • 971

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

子线程调用winform界面的 UI控件 的安全调用。delegate void SetTextCallback(string text); 创建委托private void SetText(stri...
  • winlinking
  • winlinking
  • 2009年07月14日 13:31
  • 412

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

使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。 示例 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件...
  • yenange
  • yenange
  • 2017年05月03日 11:03
  • 328

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

https://msdn.microsoft.com/zh-cn/visualc/ms171728(VS.85,printer).aspx
  • leon201211
  • leon201211
  • 2015年10月14日 11:49
  • 139

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

使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。示例访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件...
  • Tsiah
  • Tsiah
  • 2008年05月08日 20:24
  • 391

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

Windows窗体上假设有一个TextBox控件,我们需要在另外的线程中改变它的Text值。         错误的代码(非安全线程访问)如下:          private Thread d...
  • qwerty12369
  • qwerty12369
  • 2013年09月13日 16:23
  • 1274

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

使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能...
  • aaronqian
  • aaronqian
  • 2013年08月23日 17:43
  • 472

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

今天在编写c#的windows窗体程序的时候,需要用到线程来控制一个
  • jiangxindu1
  • jiangxindu1
  • 2014年10月17日 21:42
  • 652

如何跨线程调用Windows窗体控件

 在开发具有线程的应用程序时,有时会通过子线程实现Windows窗体,以及控件的操作,比如:在对文件进行复制时,为了使用户可以更好的观察到文件的复制情况,可以在指定的Windows窗体上显示一个进度条...
  • loverszhaokai
  • loverszhaokai
  • 2010年12月12日 21:06
  • 4309
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:对 Windows 窗体控件进行线程安全调用
举报原因:
原因补充:

(最多只允许输入30个字)