今天在编写一个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
分享到:
相关推荐
这就是为什么我们需要对Windows窗体控件进行线程安全调用的原因。 线程安全调用,也被称为“invoke”或“BeginInvoke”,是为了确保在正确(即UI)线程中更新UI组件。这是因为Windows窗体控件是由主线程创建和管理...
浅谈C#跨线程调用窗体控件引发的线程安全问题 C#跨线程调用窗体控件时可能会引发线程安全问题,例如当多个线程操作同一个控件时,该控件可能会进入不一致的状态,出现争用情况和死锁等问题。因此,确保以线程安全...
访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问...
标题提到的“跨线程进行Windows窗体控件的访问”是一个重要的主题,因为错误的线程交互可能引起程序崩溃或者难以调试的错误。 当一个控件由一个线程创建后,原则上只能由该线程修改。如果其他线程尝试修改同一控件...
标签“调用窗体控件”和“c#跨线程”提醒我们注意线程安全和UI更新的问题,而“跨类”则强调了对象间的通信。在实际开发中,理解这些概念对于编写健壮、高效的多线程应用程序至关重要。 总结一下,C#跨线程跨类调用...
总之,跨线程访问Windows窗体控件是多线程编程中的常见挑战,通过使用`Invoke`、`BeginInvoke`等方法,我们可以确保UI控件的安全更新。同时,正确处理线程间的通信和同步,能显著提高程序的稳定性和用户体验。在实际...
这是一个关于如何跨窗体操作控件或过程的一个例子。比如,你想用窗体A的按键来执行窗体B的文本框变色。 Imports System Imports System.Threading Imports System.Text Public Class Form1 Private Sub Form1_...
在子窗体中响应事件,可以调用主窗体的公开方法,实现对主窗体控件的操作。 6. **委托与事件处理**:在不同窗体间传递事件,可以使用委托作为方法的引用。同时,窗体间的通信可以通过定义自定义事件和事件处理程序...
在多线程编程中,尤其是Windows应用程序开发,线程间窗体控件安全调用是一个重要的概念。这个主题主要涉及到如何在多个线程之间正确、安全地操作UI(用户界面)元素,因为直接从非UI线程访问这些控件可能会导致程序...
此外,考虑到多线程安全,如果操作发生在非UI线程,还需要使用`Control.Invoke`或`Control.BeginInvoke`来同步线程。 总之,C#中的普通类调用窗体和控件主要是通过传递窗体实例,然后通过实例访问并修改控件属性。...
标题"从线程操作主窗体控件"所涉及的核心知识点是线程同步与UI线程安全。描述中提到的错误“线程间操作无效 从不是创建控件“___”的线程访问它”是.NET Framework中常见的一个异常,提示我们不能直接从非UI线程...
以下就是C#中非控件创建线程调用控件的四种主要方式: 1. **Control.Invoke() 和 Control.BeginInvoke()** 这是最常见的处理跨线程操作的方法。`Invoke`方法会同步地执行委托,直到完成才会返回,而`BeginInvoke`...
5. 控件与线程间的通信:不要直接在新线程中操作控件,而是通过信号和槽机制将信息传递回主线程。例如,如果你有一个按钮(`QPushButton`),当任务完成时,工作者对象发送一个信号,主线程中的槽函数接收到信号后...
由于Windows窗体控件(WinForms)本身并不是线程安全的,因此在多线程环境下访问这些控件时需要特别注意。不当的操作可能会导致控件进入不一致的状态、竞争条件甚至是死锁等问题。本文将详细介绍几种常见的跨线程...
### C#中跨线程调用Windows控件 在C#开发中,处理多线程与GUI(图形用户界面)的交互是一项常见的需求。当一个应用程序需要执行长时间运行的任务(如网络请求、数据库操作等)时,为了避免阻塞主线程(通常负责处理...
当我们谈论“跨窗体”调用控件时,我们通常是指在一个窗体(Form)上操作的代码想要影响或与另一个窗体上的控件交互。在这个过程中,“委托”和“回调”扮演着关键的角色。本文将深入探讨这两个概念以及它们如何在C#...
在本文中,我们将深入探讨如何在C#中利用Halcon视觉软件进行窗体控件设计。Halcon是一种强大的机器视觉软件,广泛应用于自动化行业的图像处理和识别任务。结合C#,我们可以创建用户友好的交互式应用程序,实现高效、...
在C#编程中,跨线程调用控件是一个常见的需求,特别是在开发多线程应用程序时,例如UI更新、后台任务处理等。然而,由于Windows窗体(WinForms)和WPF(Windows Presentation Foundation)应用的UI线程保护机制,...
通过上述方法,我们可以安全地从子线程中调用Windows窗体控件。这种方法不仅可以避免出现异常,还能保持应用程序的响应性。在实际开发过程中,还可以根据具体需求使用`BeginInvoke`方法来异步调用委托,进一步提高...