WPF:在Window.Closing事件中抛出未处理的异常导致e.Cancel失效
在WPF中,一种很常规的阻止窗口关闭按钮的方式,就是注册Window.Closing事件,然后在事件处理函数中设置e.Cancel=true。但是,在实践中,我遇到了这样设置不生效的情况,经过排查发现是由于Closing的事件处理函数中抛出了异常。虽然这个未处理的异常最后被全局异常处理函数捕获到了,没有导致进程的退出,但是它却使得Closing中的e.Cancel=true没有生效,导致我的窗口被关闭了。
下面,让我们看看,在Closing的处理函数中抛出异常,是如何导致窗口被关闭的。
protected virtual void OnClosing(CancelEventArgs e)
{
this.VerifyContextAndObjectState();
CancelEventHandler cancelEventHandler = (CancelEventHandler) this.Events[Window.EVENT_CLOSING];
if (cancelEventHandler == null)
return;
cancelEventHandler((object) this, e);
}
private bool WmClose()
{
if (this.IsSourceWindowNull || this.IsCompositionTargetInvalid)
return false;
this._isClosing = true;
CancelEventArgs e = new CancelEventArgs(false);
try
{
this.OnClosing(e);
}
catch
{
this.CloseWindowFromWmClose();
throw;
}
if (this.ShouldCloseWindow(e.Cancel))
{
this.CloseWindowFromWmClose();
return false;
}
this._isClosing = false;
this._dialogResult = new bool?();
return true;
}
上面是Window类中触发OnClosing事件的代码。OnClosing函数有两个地方调用,一处是Window类的公共Close方法,另一处就是这个WmClose函数,这个函数是窗口消息过程接收到WM_CLOSE的消息时调用的。
可以看到,WmClose在一个try catch块中调用了OnClosing,OnClosing调用了Closing事件的注册列表。当OnClosing抛出异常时,调用了CloseWindowFromWmClose函数。那么,是CloseWindowFromWmClose函数关闭了窗口吗?并不是,虽然CloseWindowFromWmClose函数释放了一些WPF窗口的内部资源,但没有关闭窗口。
继续查看WmClose函数可以看到,在进入函数时_isClosing字段被设置为true,退出函数时,这个字段被设置为false。如果在OnClosing中抛出了异常,_isClosing字段就不会被设置为false。那么,是这个字段导致了窗口被关闭吗?也不是,虽然这个字段能够标识窗口是否正在关闭中,但并不是窗口被关闭的直接原因。
其实,起关键作用的是WmClose函数的返回值。可以看到,它返回了一个布尔值,取下关闭窗口会返回true,不取消关闭则返回false,而如果抛出异常,则不会有返回值,而是直接抛出了异常。但是,在抛出异常前,调用了CloseWindowFromWmClose函数,就像返回false之前一样,可以猜测,抛出异常最后也会走到窗口关闭的逻辑,所以这里也需要释放掉WPF窗口的内部资源。
调用WmClose函数的是Window类里的WindowFilterMessage方法,这个方法有一个ref参数handled,WmClose的函数赋值给handled。
[SecurityCritical]
private IntPtr WindowFilterMessage(
IntPtr hwnd,
int msg,
IntPtr wParam,
IntPtr lParam,
ref bool handled)
{
//......
if (this._swh != null && this._swh.CompositionTarget != null)
{
if (windowMessage == Window.WM_TASKBARBUTTONCREATED || windowMessage == Window.WM_APPLYTASKBARITEMINFO)
{
//......
}
else
{
switch (windowMessage)
{
case WindowMessage.WM_DESTROY:
handled = this.WmDestroy();
break;
//......
case WindowMessage.WM_CLOSE:
handled = this.WmClose();
break;
//......
}
}
}
return zero;
}
WPF窗口关闭的逻辑是,接收到WM_CLOSE,触发Closing事件后,WM_CLOSE被继续传递给其他窗口过程函数来执行其他关闭窗口的代码,待窗口关闭后,会收到WM_DESTROY,触发Closed事件。在Closing事件的处理函数中,可以取消关闭,这时WmClose会返回true,这表示WM_CLOSE事件已经被处理掉了,不会再传递其他窗口过程函数,关闭窗口的代码也就不会执行。
但是这里并没有try-catch块,也就是说,WmClose中抛出的异常,会继续在调用堆栈中上行,直到某个地方被捕获掉,并且这个地方是使用参数handled的地方。
继续顺着调用堆栈找的话,可以找到HwndSubClass类的SubclassWndProc方法。
[SecurityCritical]
internal IntPtr SubclassWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
IntPtr num1 = IntPtr.Zero;
bool flag = false;
WindowMessage msg1 = (WindowMessage) msg;
if (this._bond == HwndSubclass.Bond.Unattached)
this.HookWindowProc(hwnd, new NativeMethods.WndProc(this.SubclassWndProc), Marshal.GetFunctionPointerForDelegate((Delegate) HwndSubclass.DefWndProcStub));
else if (this._bond == HwndSubclass.Bond.Detached)
throw new InvalidOperationException();
IntPtr oldWndProc = this._oldWndProc;
if (msg1 == HwndSubclass.DetachMessage)
{
if (wParam == IntPtr.Zero || wParam == (IntPtr) this._gcHandle)
{
int num2 = (int) lParam;
num1 = this.CriticalDetach(num2 > 0) ? new IntPtr(1) : IntPtr.Zero;
flag = num2 < 2;
}
}
else
{
Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
if (dispatcher != null && !dispatcher.HasShutdownFinished)
{
if (this._dispatcherOperationCallback == null)
this._dispatcherOperationCallback = new DispatcherOperationCallback(this.DispatcherCallbackOperation);
if (HwndSubclass._paramDispatcherCallbackOperation == null)
HwndSubclass._paramDispatcherCallbackOperation = new HwndSubclass.DispatcherOperationCallbackParameter();
HwndSubclass.DispatcherOperationCallbackParameter callbackOperation = HwndSubclass._paramDispatcherCallbackOperation;
HwndSubclass._paramDispatcherCallbackOperation = (HwndSubclass.DispatcherOperationCallbackParameter) null;
callbackOperation.hwnd = hwnd;
callbackOperation.msg = msg;
callbackOperation.wParam = wParam;
callbackOperation.lParam = lParam;
if (dispatcher.Invoke(DispatcherPriority.Send, (Delegate) this._dispatcherOperationCallback, (object) callbackOperation) != null)
{
flag = callbackOperation.handled;
num1 = callbackOperation.retVal;
}
HwndSubclass._paramDispatcherCallbackOperation = callbackOperation;
}
if (msg1 == WindowMessage.WM_NCDESTROY)
{
this.CriticalDetach(true);
flag = false;
}
}
if (!flag)
num1 = this.CallOldWindowProc(oldWndProc, hwnd, msg1, wParam, lParam);
return num1;
}
WindowFilterMessage方法被封装在_dispatcherOperationCallback中,callbackOperation封装了WindowFilterMessage方法的参数。flag = callbackOperation.handled这一行,相当于把WindowFilterMessage方法的handle参数赋值给flag了。结尾的CallOldWindowProc就是在调用其他的窗口过程函数。当flag为true时,代表着这个窗口消息已经被WindowFilterMessage处理掉了,就不会再调用CallOldWindowProc。
讲到这里,Closing如何取消窗口的关闭大概已经讲清楚了。但是还是没弄清楚Closing中抛出异常时,取消关闭是如何失效的,因为这里还是没有try-catch块。
但是,现在可以明确的是dispatcher.Invoke中捕获了异常,那么flag会是默认值false,窗口会继续执行关闭的逻辑。继续顺着上面代码中的dispatcher.Invoke深入,找到Dispatcher类的WrappedInvoke,callback是上面的_dispatcherOperationCallback,这里调用了ExceptionWrapper类的TryCatchWhen方法。
[FriendAccessAllowed]
internal object WrappedInvoke(
Delegate callback,
object args,
int numArgs,
Delegate catchHandler)
{
return Dispatcher._exceptionWrapper.TryCatchWhen((object) this, callback, args, numArgs, catchHandler);
}
public object TryCatchWhen(
object source,
Delegate callback,
object args,
int numArgs,
Delegate catchHandler)
{
object obj = (object) null;
try
{
obj = this.InternalRealCall(callback, args, numArgs);
}
catch (Exception ex) when (this.FilterException(source, ex))
{
if (!this.CatchException(source, ex, catchHandler))
throw;
}
return obj;
}
在TryCatchWhen方法中,终于看到了try-catch块,在catch代码中,如果CatchException方法返回true,那么这个异常就会被捕获掉。
private bool CatchException(object source, Exception e, Delegate catchHandler)
{
//......
return this.Catch != null && this.Catch(source, e);
}
CatchException方法返回的实际上是this.Catch的返回值。this是啥?这里的this实际上Dispatcher._exceptionWrapper这个静态字段所指向的实例。那么,这个实例的Catch是啥?在Dispatcher类的静态构造函数中,对Dispatcher._exceptionWrapper.Catch赋值了,它实际上封装了Dispatcher类中的静态方法CatchExceptionStatic
Dispatcher._exceptionWrapper.Catch += new ExceptionWrapper.CatchHandler(Dispatcher.CatchExceptionStatic);
private static bool CatchExceptionStatic(object source, Exception e)
{
return ((Dispatcher) source).CatchException(e);
}
private bool CatchException(Exception e)
{
bool flag1 = false;
if (this.UnhandledException != null)
{
this._unhandledExceptionEventArgs.Initialize(e, false);
bool flag2 = false;
try
{
this.UnhandledException((object) this, this._unhandledExceptionEventArgs);
flag1 = this._unhandledExceptionEventArgs.Handled;
flag2 = true;
}
finally
{
if (!flag2)
flag1 = false;
}
}
return flag1;
}
CatchExceptionStatic方法中调用的UnhandledException,就是我们注册的全局异常事件,在事件处理函数中Handle掉事件,异常就不会继续抛出了。
Dispatcher.CurrentDispatcher.UnhandledException += App_DispatcherUnhandledException;
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
e.Handled = true;
}
总结一下的话,就是Closing中抛出的异常被全局异常事件处理掉了,异常处理完之后,WM_CLOSE的handled默认是false,就继续执行了窗口关闭的逻辑。

浙公网安备 33010602011771号