问题
I need to do this in order to solve a deadlock. My Windows Forms Control has a reference to a C++/CLI class which wraps a C++ native class. The native class makes callbacks to the C++/CLI class, which maps them to events handled by the form. These callbacks are called from a thread which runs all the time.
When I want to dispose the control, I unregister all events, so that the native class can't call back anymore. Once that's done, I dispose the C++/CLI wrapper, which in turn destroys the native class. In the native class destructor, I signal the thread to end using a Windows event, and wait indefinitely for the thread to end.
However, if the thread was in the middle of a callback when the disposal starts, he might be stopped in a Control.Invoke
, and deadlock ensues. Hence the title question. Is this possible? If it were so, I could proceed this way:
- Unregister all events (native thread won't be able to call back anymore)
- Wait for all pending .Invokes to finish
- Dispose C++/CLI wrapper
- Destroy C++ native class
- Signal thread to end
- Wait for thread to end (can't deadlock with an
Invoke
since all of those finished and no more of them could have been fired since events were unregistered)
- Destroy C++ native class
- Bliss
I'm open to other suggestions for solving this problem
回答1:
There is no possibility of responding to the form closing event and wait for the thread to finish. There's an unsolvable threading race condition. And very good odds for deadlock, DoEvents would break it but is too ugly.
Implement the FormClosing event and tell the class to stop running its thread. And cancel the close. The thread should raise an event just before it exits. Use BeginInvoke() in the event handler to marshal that call to the main thread.
Now you have a guarantee that all invokes are completed since they are ordered. Furthermore, you have a guarantee that the thread can no longer generate any more events so no more invokes are coming and it is safe to unsubscribe the events (not actually necessary). Set a flag that a real close is now possible and call Close() to actually close the form.
回答2:
Option 1: Process pending callbacks via Application.DoEvents after removing the event handlers but before the destructor. Note the documentation says this handles Windows messages and does not mention if this forces processing of Invokes.
Option 2: Do not block the event thread: Call your form callbacks via BeginInvoke. Note that you might get events after the C++ object is destroyed, so add additional logic to the event handlers to ensure the C++ object has not been destroyed.
FYI, BeginInvoke
and Invoke
both use this function. BeginInvoke
sets synchronous
to false. Note that I hacked this method up a bit to remove the uninteresting portions:
private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
lock (this.threadCallbackList)
{
if (threadCallbackMessage == 0)
threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
this.threadCallbackList.Enqueue(entry);
}
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
// BeginInvoke just takes this branch instead of waiting for completion
if (!synchronous)
return entry;
if (!entry.IsCompleted)
this.WaitForWaitHandle(entry.AsyncWaitHandle);
if (entry.exception != null)
throw entry.exception;
return entry.retVal;
}
来源:https://stackoverflow.com/questions/7520663/windows-forms-is-there-a-way-to-wait-for-all-pending-invokes-to-a-control-to-en