1 UI线程执行耗时操作 UI线程被阻塞 无法响应窗体消息队列中的其他消息。
2 非UI线程修改UI属性 由于窗体资源也属于临界资源 所以有互斥访问的机制。
3 线程的同步问题 线程A等待线程B执行完毕后才能开始执行。
问题1的解决方法: 解决方法只有一种,就是开启新线程执行耗时操作,使原界面线程仍能够响应窗体消息队列中的用户消息及系统消息。
开启新线程的方式有以下各种: 1) 使用System.Threading.Thread类与System.Threading.ThreadStart委托或System.Threading.ParameterizedThreadStart委托来实现开启新线程。 ThreadStart委托的类型: void ThreadStart(void); ParameterizedThreadStart委托的类型: void ParameterizedThreadStart(object[]);
ThreadStart委托可以指向一个无参数无返回值的方法。 ParameterizedThreadStart委托可以指向一个有参数无返回值的方法。
Thread类实例化的时候可以向构造函数传入ThreadStart委托的实例或ParameterizedThreadStart委托的实例,然后使用Thread.Start()以异步方式调用一个方法。
2) 为需要异步执行的耗时方法定义一个委托,使用该委托的实例的BeginInvoke方法来异步调用该方法,BeginInvoke方法附带了AsyncCallback类型的回调函数委托以及object类型的参数。 然后可以在AsyncCallback类型的回调函数中使用EndInvoke方法来得到异步方法的返回值。
3) 可以使用System.Timers.Timer定时器类来实现在新线程中执行耗时操作,System.Timers.Timer定时器不同于System.Windows.Forms.Timer定时器,System.Timers.Timer定时器的定时事件的响应函数并不是在调用定时器Start方法的线程中去执行。
4) 可以使用BackgroundWorker组件来实现在新线程中执行耗时操作(通过订阅DoWork事件).
问题2的解决方法 以下代码是.NetFramework 2.0类库中避免多线程修改界面造成的临界资源死锁问题的代码。 System.Windows.Forms.Control.get_Handle方法的内部实现
public IntPtr get_Handle() { if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired) { throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name })); } if (!this.IsHandleCreated) { this.CreateHandle(); } return this.HandleInternal; }
在修改每个控件的属性的时候,都会先调用get_Handle方法获取一个操作句柄,在该方法内部会判断Control类的静态成员CheckForIllegalCrossThreadCalls的值(该成员用来表示是否启用安全模式,安全模式的意思就是禁止跨线程修改界面属性来避免多线程访问临界资源死锁的问题),第二个判断的属性是InvokeRequired属性(该属性用来表示当前方法是否是在跨线程调用)。 所以我们可以通过修改CheckForIllegalCrossThreadCalls属性为False来关闭安全模式,但有可能造成线程死锁问题。
解决方法只有两个 1) 设置CheckForIllegalCrossThreadCalls属性为False,关闭.net的安全模式,在对界面属性修改的代码加上lock,来实现同一时间仅有一个线程修改界面属性。 2) 在设置界面属性的方法中询问InvokeRequired属性,如果是非界面线程修改界面属性,则让界面线程来调用设置界面属性的方法。(这个方法是MSDN实例中惯用的方法,也是BackgroundWorker等组件的内部实现方式)
推荐大家使用BackgroundWorker来实现耗时操作的辅助线程以及跨线程修改界面属性等操作,关于BackgroundWorker的具体使用方法见 一日一练 之 BackgroundWorker
|