Search code examples
c#processthreadpoolmanualreseteventwaithandle

Using UnsafeRegisterWaitForSingleObject failed with exception when exiting app?


I'm trying to use ThreadPool.UnsafeRegisterWaitForSingleObject to notify if some app exits. It works at least at what I want but right after I close the main form, it throws the exception:

SEHException : External component has thrown an exception

Stack trace:

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()

Here is the code:

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);
};

I don't even need to wait the callback to be invoked, just run the code and right after that closing the main form will throw the exception.

Interestingly enough that if using OpenProcess native function to get the process handle instead of using the Process class like this:

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

it then works OK without any exception but I'm not so sure if it's better to stick to managed wrapper as much as possible in this case. Also I would like to understand why this exception is thrown when using Process class. Looks like the Synchronize flag (a required flag as documented) is what making it different between using OpenProcess and the wrapper Process. If so looks like Process cannot replace OpenProcess in this case or I missed something here?

Other info: Visual Studio 2010, targeting .NET 4.0

Thank you.


Solution

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

    That's where the problem started. You now have two SafeHandles that are wrapping the same handle. One in the Process object, another in the ManualResetEvent object. Inevitably one of them will always lose. Your code will always crash, in this case it happened because the finalizer for the MRE ran first. You have 50% odds for the other way around, happens when the Process finalizer runs first.

    The first approach is to just not do this. You already have an excellent event available to do this, use the Process.Exited event. You can even run it on the right thread so that MessageBox doesn't disappear behind another window, use its SynchronizingObject property.

    The other approach is duplicating the handle by pinvoking DuplicateHandle. Bleh. Or just address the reason why you abused this ManualResetEvent in the first place. The Process class was designed in .NET 1.0, you wouldn't have had this problem if its Handle property was SafeHandle. But its IntPtr, they couldn't fix that anymore. Fix it by deriving your own class from SafeHandle, don't do anything in the ReleaseHandle() overload.