在用.NET Framework框架的WinForm构建GUI程序界面时,若是要在控件的事件响应函数中改变控件的状态,例如:某个按钮上的文本原先叫“打开”,单击以后按钮上的文本显示“关闭”,初学者每每会想固然地这么写:框架
void ButtonOnClick(object sender,EventArgs e)函数
{ui
button.Text="关闭";this
}线程
这样的写法运行程序以后,可能会触发异常,异常信息大体是“不能从不是建立该控件的线程调用它”。注意这里是“可能”,并不必定会触发该种异常。形成这种异常的缘由在于,控件是在主线程中建立的(好比this.Controls.Add(...);),进入控件的事件响应函数时,是在控件所在的线程,并非主线程。在控件的事件响应函数中改变控件的状态,可能与主线程发生线程冲突。若是主线程正在重绘控件外观,此时在别的线程改变控件外观,就会形成画面混乱。不过这样的状况并不总会发生,若是主线程此时在重绘别的控件,就可能逃过一劫,这样的写法能够正常经过,没有触发异常。orm
正确的写法是在控件响应函数中调用控件的Invoke方法(其实若是你们之前用过C++ Builder的话,也会找到相似Invoke那样的激活到主线程的函数)。Invoke方法会顺着控件树向上搜索,直到找到建立控件的那个线程(一般是主线程),而后进入那个线程改变控件的外观,确保不发生线程冲突。正确写法的示例以下:事件
void ButtonOnClick(object sender,EventArgs e)开发
{io
button.Invoke(new EventHandler(delegateobject
{
button.Text="关闭";
}));
}
Invoke方法须要建立一个委托。你能够事先写好函数和与之对应的委托。不过,若想直观地在Invoke方法调用的时候就看到具体的函数,而不是到别处搜寻的话,上面的示例代码是不错的选择。
这样的写法有一个烦人的地方:对不一样的控件写法不一样。对于TextBox,要TextBoxObject.Invoke,对于Label,又要LabelObject.Invoke。有没有统一一点的写法呢?
主窗口类自己也有Invoke方法。若是你不想对不一样的控件写法不同,能够所有用this.Invoke:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new EventHandler(delegate
{
button.Text="关闭";
}));
}
在C# 3.0及之后的版本中有了Lamda表达式,像上面这种匿名委托有了更简洁的写法。.NET Framework 3.5及之后版本更能用Action封装方法。例如如下写法能够看上去很是简洁:
void ButtonOnClick(object sender,EventArgs e)
{
this.Invoke(new Action(()=>
{
button.Text="关闭";
}));
}
以上写法每每充斥着WinForm构建的程序。
在微软新一代的界面开发技术WPF中,因为界面呈现和业务逻辑原生态地分开在两个线程中,因此控件的事件响应函数就没必要Invoke了。可是,若是手动开辟一个新线程,那么在这个新线程中改变控件的外观,则仍是要Invoke的。