У нас есть метод, который из-за потоковой передачи в клиентском приложении требует использования 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), и он существует в службе, которая обрабатывает запросы из графического интерфейса.
Это похоже на приемлемый способ удостовериться, что мы не получаем ошибок в кросс-потоке? Если нет, есть ли лучшее решение?
Спасибо
Проследите, что происходит внутри этого метода, и посмотрите, что это говорит нам.
Подпись метода следует за обработкой событий, и, как указывает вопрос, мы можем ожидать, что она будет сначала вызвана в контексте некоторого потока, который не является потоком пользовательского интерфейса.
Первое, что делает этот метод, - сравнить SynchronizationContext потока, в котором он работает, с SynchronizationContext, сохраненным в переменной-члене. Предположим, что сохраненный контекст - это поток пользовательского интерфейса. (Майк Перец опубликовал отличную серию вступительных статей в класс SynchronizationContext на CodeProject)
Метод найдет контексты, не равные, так как он вызывается в потоке, отличном от потока пользовательского интерфейса. Контекст вызывающего потока, вероятно, будет нулевым, где контекст потока пользовательского интерфейса в значительной степени гарантирован быть установленным на экземпляр WindowsFormsSynchronizationContext. Затем он выдает Post() в контекст пользовательского интерфейса, передает делегат себе и своим аргументам и немедленно возвращается. Это завершает всю обработку фонового потока.
Вызов Post() вызывает тот же самый метод, который вызывается в потоке пользовательского интерфейса. Отслеживание реализации WindowsFormsSynchronizationContext.Post() показывает, что это реализовано путем очередности пользовательского сообщения Windows в очереди сообщений потока пользовательского интерфейса. Аргументы передаются, но не "маршалируются" в том смысле, что они не копируются и не конвертируются.
Наш метод обработчика событий теперь вызывается снова, в результате вызова Post(), с теми же самыми аргументами. Однако на этот раз поток SynchronizationContext и сохраненный контекст являются одним и тем же. Содержимое предложения if пропущено, а часть [код удалена] выполняется.
Это хороший дизайн? Трудно сказать, не зная содержание части, удаленной [code removed]. Вот несколько мыслей:
Поверхностно, это не кажется ужасным дизайном. Сообщение получено в фоновом потоке и передается в поток пользовательского интерфейса для представления. Вызывающий абонент немедленно возвращается к другим вещам, и получатель получает возможность продолжить выполнение задачи. Это несколько похоже на шаблон Unix fork().
Метод рекурсивный, единственным способом. Он не называет себя в том же потоке. Скорее, это вызывает другой поток, чтобы вызвать его. Как и в случае любой рекурсивной части кода, мы будем иметь дело с условием ее завершения. Из чтения кода представляется разумным, чтобы предположить, что он всегда будет вызываться рекурсивно ровно один раз, когда передается в поток пользовательского интерфейса. Но это еще одна проблема, о которой нужно знать. Альтернативный дизайн мог бы передать другой метод Post(), возможно, анонимный, и вообще избежать рекурсии.
Кажется, нет очевидной причины, чтобы большая часть обработки происходила внутри предложения if. Обзор реализации WindowsFormsSynchronizationContext Post() с .NET рефлектор показывает некоторые умеренно длинные последовательности кода в нем, но ничего слишком необычного; Все это происходит в ОЗУ и не копирует большие объемы данных. По сути, он просто готовит аргументы и ставит в очередь сообщение Windows в очереди сообщений получающего потока.
Вы должны просмотреть, что происходит внутри части кода, удаленной кодом. Код, который затрагивает элементы управления пользовательским интерфейсом, полностью принадлежит - он должен выполняться внутри потока пользовательского интерфейса. Однако, если там есть код, который не имеет отношения к пользовательскому интерфейсу, может быть лучше, если бы он выполнялся в принимающем потоке. Например, любой синтаксический анализ с интенсивным процессором будет лучше размещаться в принимающем потоке, где он не влияет на отзывчивость пользовательского интерфейса. Вы можете просто перенести эту часть кода выше предложения if и переместить оставшийся код в отдельный метод - чтобы обе части не выполнялись дважды.
Если и принимающий поток, и поток пользовательского интерфейса должны оставаться отзывчивыми, например, как для дальнейшего входящего сообщения, так и для ввода пользователем, вам может потребоваться ввести третий поток для обработки сообщений, прежде чем передавать их в поток пользовательского интерфейса.