SynchronizationContext.Post (…) в обработчике транспортного события

2

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

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

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

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

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

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

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

Спасибо

Теги:
multithreading
synchronization

1 ответ

6
Лучший ответ

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

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

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

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

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

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

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

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

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

  • Кажется, нет очевидной причины, чтобы большая часть обработки происходила внутри предложения if. Обзор реализации WindowsFormsSynchronizationContext Post() с .NET рефлектор показывает некоторые умеренно длинные последовательности кода в нем, но ничего слишком необычного; Все это происходит в ОЗУ и не копирует большие объемы данных. По сути, он просто готовит аргументы и ставит в очередь сообщение Windows в очереди сообщений получающего потока.

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

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

  • 0
    спасибо, отличная поломка. В конце концов, я переместил эту обработку на подкласс Stack <T>, который отслеживается с заданным интервалом. Код в области [Code Remeoved] исполняется для каждого элемента в стеке с заданным интервалом. Я полагаю, что причиной снижения производительности было то, что в разделе [Code Removed] были некоторые вызовы, которые еще не были в потоке пользовательского интерфейса, что заставляло этот вызов многократно повторяться до возможности выполнения в пользовательском интерфейсе. нить. Перемещение этих вызовов в MonitoredStack <T> решило эту проблему.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню