BusyTipOperator——显示提示窗体的实现(窗体打开与关闭过程的同步)

相较而言,窗体的打开和关闭是比较耗时的操作。其间涉及到线程、消息队列、窗口句柄等的创建和销毁。

另一方面,在其它线程上操作窗体依窗体的显示状态而不同。若在窗体打开或关闭过程未完成之前调用窗体方法或设置窗体属性,一般将导致异常:

1、窗体打开过程期间,在其它线程上获得到的InvokeRequired属性值可能仍为false(此时窗体线程尚未开始运行),此时,若直接设置窗体的属性值就将(或可能)出现那个著名的“从不是创建控件"FrmBusyTip”的线程访问它”异常(若窗体线程已开始运行),若此时关闭窗体也将出现异常。

2、窗体关闭过程期间,在其它线程上获取的InvokeRequired属性可能仍为true(此时窗体线程尚未终止),此时,若再使用Invoke函数,则可能出现“建窗口句柄之前,不能在控件上调用 Invoke ”的异常(窗体线程已终止)。

因此,在设计Start和Stop接口时,必须考虑到对窗体的打开和关闭过程的同步。

在BusyTipOperator中,对窗体打开和关闭过程的同步方法简要描述为:

1、Start和Stop方法不能重入,且使用相同的锁。

2、Start方法直到窗体打开完成才返回,Stop方法直到窗体关闭完成后才返回。

这里的重点在于第二点,而且首先要明确的一点就是如何判断窗体的打开或关闭操作是否已完成,考虑到在本功能中的需要,我们可大致认为:

1、当在其它线程上获取的InvokeRequired属性变为true时,窗体打开完成。

2、当在其它线程上获取的InvokeRequired属性变为false时,窗体关闭完成。

下面分别描述打开和关闭过程的同步方法:

一、打开过程

Start方法通过触发事件来通知TipThread线程显示窗体,触发事件的操作即刻完成,然后TipThread才开始显示窗体,因此,Start方法线程必须阻塞直到窗体创建完成。

显而易见,在系统性的操作尚未完成时,至少微软不会推荐开始执行上层逻辑。考虑到人们一般都会在Form.Load事件中进行业务初始化的工作,因此可以想象直到窗体打开完成后才会触发Load操作。因此,可在Form.Load响应中唤醒被阻塞的线程。为确定猜想,添加了一句测试代码:

ShowTipEvent.Set();

FrmOpCompletedEvent.WaitOne();
Console.WriteLine("InvokeRequired:{0}", FrmTip.InvokeRequired);

在执行结果中未发现有InvokeRequired为false的情况。

另外,阻塞/唤醒可使用两种方式:一种是Suspend/Wake,一种是使用事件。前一种有这样的危险:若在Suspend执行前,窗体就已经打开完毕,则可能出现Wake比Suspend先执行的情况。因此,只可使用第二种方式,根据AutoResetEvent的说明“如果没有等待线程,等待句柄将一直保持终止状态,直到某个线程尝试等待它,或者直到它的 Reset 方法被调用。”可知,即使AutoResetEvent.Set在AutoResetEvent.Wait之前执行,也不会出现问题。

二、关闭过程

原本以为关闭过程很好同步,因为Close应会待窗体关闭完成后才返回,所以使用阻塞函数Invoke来调用Close就应该可以了。然而测试结果却让人吃惊:

FrmTip.Invoke(new DelegateInvoke(FrmTip.Close));

Console.WriteLine("InvokeRequired:{0} After Close", FrmTip.InvokeRequired);

Thread.Sleep(100);

Console.WriteLine("InvokeRequired:{0} After Close", FrmTip.InvokeRequired);

这段代码的第一个输出是true,第二个输出则是false。可见Close在窗体线程终止前就返回了,因此不得不想办法进行同步了。

先尝试与打开过程一样的同步方法来同步,在Closed事件中唤醒原线程,发现依然不行。又绕了许多圈想了许多办法,类似这样的:

DelegateInvoke invoke = new DelegateInvoke(FrmTip.Close);
invoke += this.OpFormCompleted;

FrmTip.Invoke(new DelegateInvoke(invoke));

FrmOpCompletedEvent.WaitOne();

public void OpFormCompleted()
{
     FrmOpCompletedEvent.Set();
}

不过,最终都一一被推翻。许久后,才转了个弯想到ShowDialog应该会直到窗体关闭完成再返回吧。于是便有了TipThreadRun中的:

FrmTip.ShowDialog();
FrmOpCompletedEvent.Set();

这才总算是测试通过了。

以上就是BusyTipOperator中关于窗体打开、关闭过程同步的设计思路和过程。看似不到一百行的代码,却费了许多的工夫。最深刻的体会就是:线程同步本就是难事,再牵扯上窗体,必定更让人头疼。

原文地址:https://www.cnblogs.com/yedaoq/p/1704811.html