SynchronizationContext.Post (...) в обработчике транспортного события - PullRequest
2 голосов
/ 09 июля 2009

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

Есть фрагмент кода, который написал один из моих коллег, который мне не «кажется» правильным, и профилировщик производительности говорит мне, что в этом фрагменте кода используется большая часть обработки.

void transportHelper_SubscriptionMessageReceived(object sender, SubscriptionMessageEventArgs e)
        {
            if (SynchronizationContext.Current != synchronizationContext)
            {
                synchronizationContext.Post(delegate
                     {
                         transportHelper_SubscriptionMessageReceived(sender, e);
                     }, null);

                return;
            }
  [code removed....]
}

Мне это просто не подходит, поскольку мы в основном отправляем один и тот же запрос в очередь событий потока графического интерфейса ... однако я также не вижу никаких проблем, кроме производительности этой области кода.

Этот метод является обработчиком событий, прикрепленным к событию, вызванному нашим помощником уровня обмена сообщениями среднего уровня (transportHelper), и он существует в службе, которая обрабатывает запросы из графического интерфейса.

Похоже ли это на приемлемый способ убедиться, что мы не получаем межпоточных ошибок? Если нет, есть ли лучшее решение?

Спасибо

1 Ответ

6 голосов
/ 09 июля 2009

Давайте проследим, что происходит внутри этого метода, и посмотрим, что это говорит нам.

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

  2. Первое, что делает метод, - это сравнивает SynchronizationContext потока, в котором он выполняется, с SynchronizationContext, сохраненным в переменной-члене. Мы будем предполагать, что сохраненный контекст является темой потока пользовательского интерфейса. (Майк Перец опубликовал отличную серию вводных статей для класса SynchronizationContext на CodeProject )

  3. Метод найдет контексты не равными, так как он вызывается в потоке, отличном от потока пользовательского интерфейса. Контекст вызывающего потока, скорее всего, будет нулевым, поскольку контекст потока пользовательского интерфейса в значительной степени гарантированно будет установлен на экземпляр WindowsFormsSynchronizationContext. Затем он выдаст Post () в контексте пользовательского интерфейса, передавая делегат самому себе и его аргументам, и немедленно вернется. Это завершает всю обработку в фоновом потоке.

  4. Вызов Post () вызывает тот же метод, который вызывается в потоке пользовательского интерфейса. Отслеживание реализации WindowsFormsSynchronizationContext.Post () показывает, что это реализуется путем помещения в очередь пользовательского сообщения Windows в очереди сообщений потока пользовательского интерфейса. Аргументы передаются, но не «маршалируются» в том смысле, что они не копируются и не конвертируются.

  5. Наш метод-обработчик событий теперь вызывается снова, в результате вызова Post (), с точно такими же аргументами. Однако на этот раз SynchronizationContext потока и сохраненный контекст - одно и то же. Содержимое предложения if пропускается, и выполняется часть [код удален].

Это хороший дизайн? Трудно сказать, не зная содержания части [код удален]. Вот некоторые мысли:

  1. Внешне это не кажется ужасным дизайном. Сообщение принимается в фоновом потоке и передается в поток пользовательского интерфейса для представления. Вызывающий абонент немедленно возвращается для выполнения других задач, а получатель получает возможность продолжить выполнение задачи. Это несколько похоже на шаблон Unix fork ().

  2. Метод рекурсивный, уникальным способом. Он не вызывает себя в том же потоке. Скорее, это вызывает другой поток, чтобы вызвать его. Как и в случае с любым рекурсивным фрагментом кода, нас интересует условие его завершения. После прочтения кода представляется достаточно безопасным предположить, что он всегда будет вызываться рекурсивно ровно один раз при передаче в поток пользовательского интерфейса. Но это еще одна проблема, о которой нужно знать. Альтернативный дизайн мог бы передать в Post () другой метод, возможно, анонимный, и полностью избежать проблемы рекурсии.

  3. Кажется, нет очевидной причины для большого количества обработки внутри предложения if. Анализ реализации PostFor в WindowsFormsSynchronizationContext с отражателем .NET обнаруживает некоторые умеренно длинные последовательности кода, но ничего особенного; Все это происходит в оперативной памяти и не копирует большие объемы данных. По сути, он просто подготавливает аргументы и ставит в очередь сообщение Windows в очереди сообщений принимающего потока.

  4. Вы должны проанализировать, что происходит внутри части метода [код удален]. Код, который касается элементов управления пользовательского интерфейса, полностью принадлежит ему - он должен выполняться внутри потока пользовательского интерфейса. Однако, если там есть код, который не имеет отношения к пользовательскому интерфейсу, возможно, лучше выполнить его в принимающем потоке. Например, любой анализ с интенсивным использованием ЦП лучше размещать в принимающем потоке, где это не влияет на скорость отклика пользовательского интерфейса. Вы можете просто переместить эту часть кода над предложением if и переместить оставшийся код в отдельный метод - чтобы гарантировать, что ни одна из частей не будет выполнена дважды.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...