参考自:http://msdn.microsoft.com/ZH-CN/library/SYSTEM.WINDOWS.FORMS.CONTROL.INVOKE.aspx
如果使用多线程来提高 Windows 窗体应用程序的性能,则必须确保以线程安全方式调用控件。
访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能会出现其他与线程相关的 Bug,例如争用情况和死锁。
确保以线程安全方式访问控件非常重要。
在未使用 Invoke 方法的情况下,从不是创建某个控件的线程的其他线程调用该控件是不安全的。
.NET Framework 可帮助检测”以非线程安全方式访问控件“这一问题。在调试器中运行应用程序时,如果一个不是创建某个控件的线程的其他线程调用该控件,则调试器会引发一个InvalidOperationException,并显示以下消息:“从不是创建控件‘控件名称’的线程访问它。”
方法一:Control.Invoke()方法
同一个类中比较简单,直接用委托就可以实现,步骤如下:
(1) 定义一个委托,或使用系统提供的委托,如:MethodInvoke
(2) 定义一个符合委托类型的函数,包含具体的对控件的操作;
(3) 在线程的回调函数中,定义委托对象,然后用Control.Invoke()或Control.BeginInvoke()进行UI委托操作
简单示例:
举个简单例子说明下使用方法,比如你在启动一个线程,在线程的方法中想更新窗体中的一个TextBox
//定义一个委托
public delegate void MyInvoke(string,string);
//线程回调函数
public void DoWork()
{
//委托对象
MyInvoke mi = new MyInvoke(UpdateForm);
//异步调用委托
this.BeginInvoke(mi, new Object[] {"我是文本框","haha"});
}
//符合委托的函数,进行TextBox的操作
public void UpdateForm(string param1,string parm2)
{
this.textBox1.Text = param1+parm2;
}
不同类中则要借助事件来实现,步骤如下:
(1) 事件触发者类:
1> 定义一个委托;
2> 定义一个该委托类型的事件;
3> 触发事件;
(2) 事件响应者类:
1> 定义一个函数,用来处理事件响应;
2> 订阅事件,并为该事件添加响应;
简单示例:
在多线程中接收消息,然后在ListBox中进行显示
在事件触发类中,定义“MsgEvent”事件;
在事件响应类中,订阅该事件,并在ListBox中进行显示;
事件触发类代码如下:
public class MsgRecver
{
//创建一个委托,返回类型为void,两个参数
public delegate void MsgEventHandle(object sender, MsgEventArgs e);
//将创建的委托和特定事件关联,在这里特定的事件为MsgEvent
public event MsgEventHandle MsgEvent;
// 触发事件
MsgEvent(this, Args);
}
事件响应类代码如下:
public class MsgHandle
{
//注意,构造函数中传入事件发生类的对象,只是为了订阅事件用
public EventResponse(MsgRecver msgRecver)
{
//产生一个委托实例并添加到MsgRecver产生的事件响应列表中
msgRecver.msgEventHandle += new msgRecver.MsgEventHandle(Echo);
}
private void Echo(object sender, MsgEventArgs e)
{
// 真正的事件处理函数
this.listBox1.Text+=e.Message;
}
}
-
方法二:使用 BackgroundWorker 进行线程安全调用
在应用程序中实现多线程的首选方式是使用BackgroundWorker 组件。 BackgroundWorker 组件使用事件驱动模型实现多线程。
后台线程运行 DoWork 事件处理程序,而创建控件的线程运行 ProgressChanged 和 RunWorkerCompleted 事件处理程序。
可以从 ProgressChanged 和 RunWorkerCompleted 事件处理程序调用控件。
步骤如下:
(1) 创建一个方法,该方法用于执行您希望在后台线程中完成的工作。
注意:不要调用由此方法中的主线程创建的控件。
(2) 创建一个方法,用于在后台工作完成后报告结果。可以调用由此方法中的主线程创建的控件。
(3) 将步骤 1 中创建的方法绑定到 BackgroundWorker 的实例的 DoWork 事件
(4) 将步骤 2 中创建的方法绑定到同一实例的 RunWorkerCompleted 事件。
(5) 若要启动后台线程,请调用 BackgroundWorker 实例的 RunWorkerAsync() 方法。