退出应用程序时使用 UnsafeRegisterWaitForSingleObject 失败并出现异常?

Using UnsafeRegisterWaitForSingleObject failed with exception when exiting app?

我正在尝试使用 ThreadPool.UnsafeRegisterWaitForSingleObject 通知某些应用程序是否退出。它至少可以按我的要求工作,但在我关闭主窗体后,它会抛出异常:

SEHException : External component has thrown an exception

堆栈跟踪:

at Microsoft.Win32.SafeNativeMethods.CloseHandle(IntPtr handle)
at Microsoft.Win32.SafeHandles.SafeProcessHandle.ReleaseHandle()
at System.Runtime.InteropServices.SafeHandle.InternalFinalize()
at System.Runtime.InteropServices.SafeHandle.Dispose(Boolean disposing)
at System.Runtime.InteropServices.SafeHandle.Finalize()

代码如下:

Load += (s, e) => {
   var p = System.Diagnostics.Process.GetProcessById(8524).Handle;
   var wh = new ManualResetEvent(false);
   wh.SafeWaitHandle = new SafeWaitHandle(p, true);                
   var cl = ThreadPool.UnsafeRegisterWaitForSingleObject(
                                            wh, new WaitOrTimerCallback((o, b) => 
                {
                    MessageBox.Show("Exited!");
                }), null, Timeout.Infinite, true);
};

我什至不需要等待调用回调,只需 运行 代码,关闭主窗体后立即抛出异常。

有趣的是,如果使用 OpenProcess 本机函数而不是像这样使用 Process class 来获取进程句柄:

//ProcessAccessFlags.Synchronize = 0x00100000
var p = OpenProcess(ProcessAccessFlags.Synchronize, false, 8524);

然后它毫无例外地工作正常,但我不确定在这种情况下是否最好坚持使用托管包装器。另外我想了解为什么在使用 Process class 时抛出此异常。看起来 Synchronize 标志(记录的必需标志)是使用 OpenProcess 和包装器 Process 之间的区别所在。如果在这种情况下看起来 Process 无法替换 OpenProcess 或者我在这里遗漏了什么?

其他信息:Visual Studio 2010,针对 .NET 4.0

谢谢。

wh.SafeWaitHandle = new SafeWaitHandle(p, true); 

这就是问题开始的地方。您现在有 两个 个 SafeHandles 包装同一个句柄。一个在 Process 对象中,另一个在 ManualResetEvent 对象中。不可避免地,其中之一将永远失败。您的代码总是会崩溃,在这种情况下,它的发生是因为 MRE 运行 的终结器首先出现。当 Process finalizer 运行s 首先出现时,你有 50% 的可能性相反。

第一种方法就是不这样做。您已经有一个很棒的事件可以执行此操作,请使用 Process.Exited event。您甚至可以 运行 它在正确的线程上,这样 MessageBox 就不会消失在另一个 window 后面,使用它的 SynchronizingObject 属性.

另一种方法是通过调用 DuplicateHandle 来复制句柄。呸。或者只是解决您最初滥用此 ManualResetEvent 的原因。 Process class 是在.NET 1.0 中设计的,如果它的Handle 属性 是SafeHandle,你就不会遇到这个问题。但它的 IntPtr,他们无法再修复它了。通过从 SafeHandle 派生您自己的 class 来修复它,不要在 ReleaseHandle() 重载中做任何事情。