Have just had an idea, I haven't seen it before, wondering if you guys thought it was a good idea, if it exists, any common pitfalls etc. - and also how to implement it.
There are several times I've found myself subscribing to an event from the UI thread that will be called from a different thread - for example, notification of a service call completing.
'My' idea would be to store the current Dispatcher
in the add
block along with the handler delegate, then when the event is 'fired', perform some extra logic/checks to see if there was a dispatcher associated with the handler, and Invoke
on it if necessary.
Of course it would only work on threads with a Dispatcher
(or Forms equivalent - something with a message pump I guess). I guess the usefulness and cleanliness depends on whether the event subscriber should have to worry about the thread the handler is called or not?
Edit: Sounds like it's not such a bad thing then - additionally does anyone have any idea how to implement? Using Delegate.Combine
how could you call each handler on a different Dispatcher
, for example? Would you instead store delegates in a composite object in a List
, and invoke them in turn in the On(Whatever)
method, or is there something nicer?
...Looking at the BackgroundWorker
source in Reflector, there's nothing to Invoke:
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
ProgressChangedEventHandler handler = (ProgressChangedEventHandler) base.Events[progressChangedKey];
if (handler != null)
{
handler(this, e);
}
}
Unless I'm missing something?
So then BackgroundWorker
does it with an AsyncOperation
. How about a general solution, just for event handlers, in the event accessors? BackgroundWorker
can get away with the way it works because a method is called from the client - in the more general case, the only time you'll have access to the handler's thread is in the event accessor? :)
I've done something similar with Castle DynamicProxy, where it intercepts calls and does an
IsInvokeRequired/Invoke
on them.As far as I know, that's exactly what the
BackgroundWorker
is doing in itsRunWorkerCompleted
andProgressChanged
events. So it can't be that bad.I can't find a real proof, that the
BackgroundWorker
is doing it, I just read it somewhere. When you google for it, you will find more hints. If someone can provide a link, I would be happy.UPDATE:
Because it isn't so easy to find this behavior in the BackgroundWorker, I provide my analysis:
The
BackgroundWorker
is using anAsyncOperation
for raising the events. Inside this class, the events are posted to aSynchronizationContext
. Only then are the methodsOnProgressChanged
andOnRunWorkerCompleted
executed. This means, those methods are already executed on the right thread.In some more detail, the following happens, when
RunWorkerAsync
is called:AsyncOperation
instance is created viaAsyncOperationManager.CreateOperation
. This saves the currentSynchronizationContext
. As we are still in the UI thread, this is the context of the UI thread.WorkerThreadStart
. This method is running in the background thread and executesOnDoWork
which in turn raises theDoWork
event. This means, theDoWork
event is not raised in the UI thread.OnDoWork
completed, thePostOperationCompleted
method of theAsyncOperation
instance is executed which in turn callsAsyncOperation.Post
which callsSynchronizationContext.Post
which in turn will call indirectlyOnRunWorkerCompleted
on the UI thread.ReportProgress
is called, a similar thing happens:AsyncOperation.Post
is called directly and will invoke theOnProgressChanged
method on the UI thread.AsyncOperation
andAsyncOperationManager
are public and can be used to implement a similar behavior in your classes.