最近看了相关的一些这方面的内容,做一个简单的笔记,主要为了备忘,也许能帮上有缘的人。
STA模型是一种线程模型,这缘于Win32窗口的需要,在Win32系统中的窗口都有一个句柄,这个窗口句柄与创建它的线程的句柄是被联系在一起的(这也是为什么消息泵中的DispathMessage函数能正确投递消息到对应窗口的WndProc的原因)。
为了简单的说明问题,假设线程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。
个人推荐方法二,但是为什么BackgroundWorker的ProgressChanged可以被保留在原线程执行?这是笔者还不懂的问题,要知道,一般情况下,触发一个event,就是在本线程执行EventHandler,难道也使用了方法一中的Invoke?
不清楚,总觉得一个线程让另一个线程停下来执行一段代码,很是奇妙。希望清楚的兄弟谈谈自己的看法,相互交流一下。