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

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

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

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

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


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

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

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

Если я чего-то не упускаю?


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


person Kieren Johnstone    schedule 07.04.2011    source источник


Ответы (2)


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

ОБНОВЛЕНИЕ:
Поскольку найти такое поведение в 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 и _ 28_ являются общедоступными и могут использоваться для реализации аналогичного поведения в ваших классах.

person Daniel Hilgarth    schedule 07.04.2011
comment
Совершенно верно - это одна из вещей, которая упрощает работу с фоновым исполнителем - вы уже находитесь в нужном потоке в обработчиках обратного вызова. Также см. Этот поток SO: stackoverflow.com/questions/670526 / - person BrokenGlass; 07.04.2011
comment
Я быстро просмотрел BackgroundWorker источник и ничего не нашел - вопрос обновлен - я что-то упускаю? - person Kieren Johnstone; 07.04.2011
comment
@BrokenGlass: О вашей ссылке: поиск в Google выдает такие вещи, но ничего официального ... - person Daniel Hilgarth; 07.04.2011
comment
@ Кирен Джонстон: Эй, теперь это интересно! - person Daniel Hilgarth; 07.04.2011
comment
@Kieren и @BrokenGlass: Пожалуйста, ознакомьтесь с обновлением моего ответа для анализа этой функции в классе BackgroundWorker. - person Daniel Hilgarth; 07.04.2011

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

person kͩeͣmͮpͥ ͩ    schedule 07.04.2011