在用CS结构开发系统时,我们常会使用到多线程来处理程序,其中一个就是使用跨线程访问控件或设置控件属性。如果这样,在调试时会提示出错,不能在其它线程中修改创建控件的线程的控件的值。这是因为.net 2.0以后加强了安全机制,不允许在winform中直接跨线程访问控件的属性。
我们先看一下传统的程序:
设计界面如下:在窗体中添加一个Label控件(用来显示时间)
源程序:
using System;
using System.Windows.Forms;
using System.Threading;
namespace KingChat
{
public partial class SystemLogin : DevExpress.XtraEditors.XtraForm
{
public SystemLogin()
{
InitializeComponent();
}
private void SystemLogin_Load(object sender, EventArgs e)
{
Thread newThread = new Thread(newThreadStart(threadFuntion));
newThread.IsBackground = true;
newThread.Start();
}
private void threadFuntion()
{
while (true)
{
this.label1.Text =DateTime.Now.ToString();
Thread.Sleep(1000);
}
}
}
}
当调试上面的程序是会提示出错。
原因是:.NET 原则上禁止跨线程访问控件,因为这样可能造成错误的发生。
那么我们有什么方法解决C#跨线程访问控件问题:
1)禁止编译器对跨线程访问作检查,可以实现访问,但是出不出错不敢保证;
2)推荐的解决方法是采用代理,用代理方法来间接操作不是同一线程创建的控件。
解决方案一:
利用Control.CheckForIllegalCrossThreadCalls = false;加入这句代码调试时也不会提示出错,可以正常执行下面的程序。这句代码就是说在这个类中我们不检查跨线程的调用是否合法(如果没有加这句话运行也没有异常,那么说明系统以及默认的采用了不检查的方式)。然而,这种方法不可取。我们查看CheckForIllegalCrossThreadCalls这个属性的定义,就会发现它是一个static的,也就是说无论我们在项目的什么地方修改了这个值,他就会在全局起作用。而且像这种跨线程访问是否存在异常,我们通常都会去检查。如果项目中其他人修改了这个属性,那么我们的方案就失败了,我们要采取另外的方案。
具体做法如下:
在窗体的Load方法中加入代码,其他不变
private void SystemLogin_Load(object sender, EventArgs e)
{
Control.CheckForIllegalCrossThreadCalls =false;
Thread newThread = new Thread(newThreadStart(threadFuntion));
newThread.IsBackground = true;
newThread.Start();
}
解决方案二:
就是使用delegate和invoke来从其他线程中控制控件信息。
源代码如下:
using System;
using System.Windows.Forms;
using System.Threading;
namespace KingChat
{
public partial class SystemLogin : DevExpress.XtraEditors.XtraForm
{
private delegate void DelegateFunction();//代理
public SystemLogin()
{
InitializeComponent();
}
private void SystemLogin_Load(object sender, EventArgs e)
{
Thread thread =new Thread(CrossThreadFlush);
thread.IsBackground = true;
thread.Start();
}
private void CrossThreadFlush ()
{
while (true)
{
Thread.Sleep(1000);
ThreadFunction();
}
}
private void ThreadFunction()
{
if (this.label1.InvokeRequired)//等待异步
{
DelegateFunction df =new DelegateFunction(ThreadFunction);
this.Invoke(df);//通过代理调用刷新方法
}
else
{
this.label1.Text =DateTime.Now.ToString();
}
}
}
}
我们可以看到问题已经被解决了,通过等待异步,我们就不会总是持有主线程的控制,这样就可以在不发生跨线程调用异常的情况下完成多线程对winform多线程控件的控制了。
刚运行可执行文件时会先显示Label控件的Text值,大概一秒后就可以看到时间:
图4 图5
解决方案二的优化:
利用了delegate的异步调用。
using System;
using System.Windows.Forms;
using System.Threading;
namespace KingChat
{
public partial class SystemLogin : DevExpress.XtraEditors.XtraForm
{
private delegate void DelegateFunction();//代理
public SystemLogin()
{
InitializeComponent();
}
private void SystemLogin_Load(object sender, EventArgs e)
{
Thread thread =new Thread(CrossThreadFlush);
thread.IsBackground = true;
thread.Start();
}
private void CrossThreadFlush ()
{
DelegateFunction df =new DelegateFunction(ThreadFunction);
df.BeginInvoke(null,null);
}
private void ThreadFunction()
{
while (true)
{
this.label1.Text =DateTime.Now.ToString();
Thread.Sleep(1000);
}
}
}
}