.net Thread process UI
假设线程A创建了窗口W,同时为了工作需要线程A有创建了工作线程B。
STA中,对窗口W中与UI(界面)相关属性的修改,必须在创建窗口的线程A中完成。换句话说,线程B不允许修改W的UI属性(否则抛出InvalidOperationException,消息“Control control name accessed from a thread other than the thread it was created on”)。从理论上讲,这种限制是合理的,因为只有从线程A的句柄才能找到窗口W的句柄,从而修改其外观。笔者我也不清楚在Windows Vista中的Avalon会不会改掉窗口线程的模式,毕竟STA模式不适十分方便,且容易引起线程安全问题。
如果能确保线程B不执行修改W的UI属性的代码,这称为Thread-safed线程安全。.Net中几乎所有的UI组件都是线程安全的,虽然当中有些组件含有多个线程。
当我们创建自定义控件时,也要注意Thread-safed问题。但有时,线程B的确有修改窗口W的UI属性的需要,如何是好?基本上,有两个解决办法。
方法一:在线程B中调用Control::Invoke(Delegate)或Control::BeginInvoke(Delegate)函数(例如:W->Invoke(d))前者是同步调用,后者异步调用,其中的委托执行修改窗口W的UI属性的代码。这俩个函数的功能是,将Delegate强制放到线程A中执行。
方法二:线程B不用Thread类创建,而用BackgroundWorker类创建。BackgroundWorker类通过DoWork、ProgressChanged、RunWorkerCompleted三个事件(event)驱动的。DoWork最为重要,它的EventHandler将被执行与新的线程(本例中的线程B),通过调用BackgroundWorker::RunWorkerAsync()触发DoWork。后两个事件的EventHandler将被执行于原线程(本例中的线程A),ProgressChanged由BackgroundWorker::ReportProgress (Int32) 触发,RunWorkerCompleted在DoWork干完事被触发。如此一来,我们可以将修改窗口UI属性的代码放入事件ProgressChanged的EventHandler中。当线程B(DoWork)希望修改窗口UI属性时,用ReportProgress (Int32)触发事件ProgressChanged,由后者的EventHandler完成修改,而它正是执行于线程A。