Умные средства доступа к событиям - запускать обработчики в потоке, в котором они были зарегистрированы? - PullRequest
0 голосов
/ 07 апреля 2011

Только что пришла идея, я не видел ее раньше, задаваясь вопросом, думали ли вы, ребята, что это хорошая идея, если она существует, какие-либо распространенные ошибки и т. Д. - а также как ее реализовать.

Несколько раз я обнаруживал, что подписываюсь на событие из потока пользовательского интерфейса, которое будет вызываться из другого потока - например, уведомление о завершении вызова службы.

Моя идея состояла бы в том, чтобысохраните текущий Dispatcher в блоке add вместе с делегатом обработчика, затем, когда событие 'запущено', выполните некоторые дополнительные логические операции / проверки, чтобы увидеть, был ли диспетчер, связанный с обработчиком, и Invoke наэто при необходимости.

Конечно, это будет работать только с потоками с Dispatcher (или эквивалент Forms - что-то с насосом сообщений, я думаю).Я полагаю, что полезность и чистота зависят от того, должен ли подписчик события беспокоиться о потоке, вызываемом обработчиком, или нет?


Редактировать: Похоже, это не так уж плохо - кроме того, кто-нибудьесть идеи как реализовать?Используя Delegate.Combine, как, например, вы могли бы вызывать каждый обработчик для другого Dispatcher?Вместо этого вы бы хранили делегаты в составном объекте в List и вызывали их по очереди в методе On(Whatever), или есть что-то более приятное?

... Глядя на источник BackgroundWorker вОтражатель, вызывать нечего:

protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
    ProgressChangedEventHandler handler = (ProgressChangedEventHandler) base.Events[progressChangedKey];
    if (handler != null)
    {
        handler(this, e);
    }
}

Если я что-то упустил?


Итак, BackgroundWorker делает это с AsyncOperation.Как насчет общего решения, только для обработчиков событий, в средствах доступа к событиям?BackgroundWorker может сойти с рук, как он работает, потому что метод вызывается из клиента - в более общем случае, единственный раз, когда вы будете иметь доступ к потоку обработчика, находится в методе доступа к событию?:)

Ответы [ 2 ]

4 голосов
/ 07 апреля 2011

Насколько я знаю, именно это и делает BackgroundWorker в своих RunWorkerCompleted и ProgressChanged событиях. Так что не может быть что плохо.
Я не могу найти реального доказательства того, что BackgroundWorker делает это, я просто где-то читал. Когда вы Google для него , вы найдете больше подсказок. Если кто-то может предоставить ссылку, я был бы счастлив.

UPDATE:
Поскольку это не так просто найти в BackgroundWorker, я приведу свой анализ:
BackgroundWorker использует AsyncOperation для вызова событий. Внутри этого класса события публикуются в SynchronizationContext. Только тогда выполняются методы OnProgressChanged и OnRunWorkerCompleted. Это означает, что эти методы уже выполняются в нужном потоке.

Более подробно, когда вызывается RunWorkerAsync, происходит следующее:

  1. Экземпляр AsyncOperation создается с помощью AsyncOperationManager.CreateOperation. Это сохраняет текущий SynchronizationContext. Поскольку мы все еще в потоке пользовательского интерфейса, это контекст потока пользовательского интерфейса.
  2. Фоновая операция запускается и вызывает частный метод WorkerThreadStart. Этот метод выполняется в фоновом потоке и выполняет OnDoWork, что, в свою очередь, вызывает событие DoWork. Это означает, что событие DoWork не вызвано в потоке пользовательского интерфейса.
  3. После завершения OnDoWork выполняется метод PostOperationCompleted экземпляра AsyncOperation, который, в свою очередь, вызывает AsyncOperation.Post, что вызывает SynchronizationContext.Post, что, в свою очередь, косвенно вызывает OnRunWorkerCompleted в потоке пользовательского интерфейса.
  4. Когда вызывается ReportProgress, происходит похожая вещь: AsyncOperation.Post вызывается напрямую и вызывает метод OnProgressChanged в потоке пользовательского интерфейса.

AsyncOperation и AsyncOperationManager являются общедоступными и могут использоваться для реализации аналогичного поведения в ваших классах.

0 голосов
/ 07 апреля 2011

Я сделал нечто подобное с Castle DynamicProxy, где он перехватывает вызовы и делает IsInvokeRequired/Invoke для них.

...