Reputation: 135433
We have some code running in a background thread which needs to pop a dialog or some other user interaction, so we do the usual Invoke
call on to the UI thread:
Control.Invoke(SomeFunction);
void SomeFunction()
{
...
}
But, we came across a bug, our UI thread is sometimes not immediately responding to the Invoke
call - we tracked it down to the fact that the UI thread was currently performing a cross process DCOM call that hadn't returned yet. Once the DCOM call had returned our function would be called, but until then it appeared that the Invoke
call had hung.
My solution for this was to introduce a timeout:
ManualResetEvent invokeEvent = new ManualResetEvent();
var result = Control.BeginInvoke(SomeFunction, invokeEvent);
if (!invokeEvent.WaitOne(1000))
throw new Exception("Not responding");
Control.EndInvoke(result);
void SomeFunction(ManualResetEvent invokeEvent)
{
invokeEvent.Set();
...
}
This worked in the "works on my machine sense", but it had a number of flaws.
(source: codinghorror.com)
Even if the first two things could be resolved, we still have the general ickyness. Is there a better way to resolve this?
Upvotes: 1
Views: 3836
Reputation: 3159
This is a common threading problem when it comes to running something on the GUI thread and this symptom affects all breeds of developers.
If you were to create a separate thread where you display the actual progress dialog and another thread to perform the DCOM call, it would simply entail moving the ManuaResetEvent synchronization between the two threads. This has the benefit of not locking the GUI thread since the separate thread that creates the progress form will have its own message queue created, and the second thread used to run the DCOM call does not have to lock any GUI thread.
It does require some careful sycnhronizing but once done, it's beautiful to see in action:
private ManualResetEvent _event = new ManualResetEvent(false);
...
private void StartTheComProgressCall()
{
_event.Reset();
ThreadPool.QueueUserWorkItem(StartProgressDialog);
ThreadPool.QueueUserWorkItem(StartDCOMCall);
// there's various possibilities to perform here, we could ideally 1) wait on the
// event to complete, 2) run a callback delegate once everything is done
// 3) fire an event once completed
}
private void StartProgressDialog(object state)
{
ProgressDialog dialog = new ProgressDialog();
dialog.Show();
while(!_event.WaitOne(0))
Application.DoEvents();
dialog.Close();
}
private void StartDCOMCall()
{
...
<perform your DCOM routines here>
// once the call is done, remember to trigger that it's complete
// so that blocking threads can continue to do what they need to do
_event.Set();
}
Notes
Some might argue against using the Application.DoEvents()
method, but consider that DoEvents
forces any pending Windows messages on the current calling thread's message queue to be processed and since the call is made in a different thread (the one that created the progress dialog) and not the GUI thread, there should be no more or ethical "code smell" issues with using it. We should use whatever tools or technique help us get the job done.
Upvotes: 0
Reputation: 99989
Move the cross-process DCOM call to another thread. You are obviously hanging the UI thread, which is completely unacceptable. Fix that and your phantom problem (the OP) goes away too.
Upvotes: 0