问题
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? :)
回答1:
As far as I know, that's exactly what the BackgroundWorker is doing in its RunWorkerCompleted and ProgressChanged 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 an AsyncOperation for raising the events. Inside this class, the events are posted to a SynchronizationContext. Only then are the methods OnProgressChanged and OnRunWorkerCompleted executed. This means, those methods are already executed on the right thread.
In some more detail, the following happens, when RunWorkerAsync is called:
- An
AsyncOperationinstance is created viaAsyncOperationManager.CreateOperation. This saves the currentSynchronizationContext. As we are still in the UI thread, this is the context of the UI thread. - The background operation is started and calls into the private method
WorkerThreadStart. This method is running in the background thread and executesOnDoWorkwhich in turn raises theDoWorkevent. This means, theDoWorkevent is not raised in the UI thread. - After
OnDoWorkcompleted, thePostOperationCompletedmethod of theAsyncOperationinstance is executed which in turn callsAsyncOperation.Postwhich callsSynchronizationContext.Postwhich in turn will call indirectlyOnRunWorkerCompletedon the UI thread. - When
ReportProgressis called, a similar thing happens:AsyncOperation.Postis called directly and will invoke theOnProgressChangedmethod on the UI thread.
AsyncOperation and AsyncOperationManager are public and can be used to implement a similar behavior in your classes.
回答2:
I've done something similar with Castle DynamicProxy, where it intercepts calls and does an IsInvokeRequired/Invoke on them.
来源:https://stackoverflow.com/questions/5583503/clever-event-accessors-to-fire-handlers-on-the-thread-they-were-registered