Clever event accessors - to fire handlers on the t

2019-09-14 13:51发布

问题:

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:

  1. An AsyncOperation instance is created via AsyncOperationManager.CreateOperation. This saves the current SynchronizationContext. As we are still in the UI thread, this is the context of the UI thread.
  2. The background operation is started and calls into the private method WorkerThreadStart. This method is running in the background thread and executes OnDoWork which in turn raises the DoWork event. This means, the DoWork event is not raised in the UI thread.
  3. After OnDoWork completed, the PostOperationCompleted method of the AsyncOperation instance is executed which in turn calls AsyncOperation.Post which calls SynchronizationContext.Post which in turn will call indirectly OnRunWorkerCompleted on the UI thread.
  4. When ReportProgress is called, a similar thing happens: AsyncOperation.Post is called directly and will invoke the OnProgressChanged method 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.