Я играл с дизайном типа DataBus для хобби-проекта, и столкнулся с проблемой. Внутренние компоненты должны уведомлять пользовательский интерфейс о том, что что-то произошло. Моя реализация шины доставляет сообщения синхронно по отношению к отправителю. Другими словами, когда вы вызываете Send()
, метод блокируется, пока не будут вызваны все обработчики. (Это позволяет вызывающим сторонам использовать управление стековой памятью для объектов событий.)
Однако рассмотрим случай, когда обработчик событий обновляет графический интерфейс в ответ на событие. Если вызывается обработчик, а отправитель сообщения живет в другом потоке, то обработчик не может обновить графический интерфейс из-за того, что элементы графического интерфейса Win32 имеют привязку к потоку. Более динамичные платформы, такие как .NET, позволяют вам обрабатывать это, вызывая специальный метод Invoke () для перемещения вызова метода (и аргументов) в поток пользовательского интерфейса. Я предполагаю, что они используют парковочное окно .NET или тому подобное для такого рода вещей.
Родилось болезненное любопытство: можем ли мы сделать это на C ++, даже если мы ограничим масштаб проблемы? Можем ли мы сделать это лучше, чем существующие решения? Я знаю, что Qt делает нечто подобное с функцией moveToThread()
.
Приятнее упомянуть, что я специально пытаюсь избежать кода следующей формы:
if(! this->IsUIThread())
{
Invoke(MainWindowPresenter::OnTracksAdded, e);
return;
}
на вершине каждого метода пользовательского интерфейса. Этот танец был обычным явлением в WinForms при решении этой проблемы. Я думаю, что такого рода проблемы должны быть изолированы от предметно-ориентированного кода и объекта-обертки, созданного для его решения.
Моя реализация состоит из:
DeferredFunction - функтор, который сохраняет целевой метод в FastDelegate и копирует один аргумент события. Это объект, который отправляется через границы потока.
UIEventHandler - отвечает за отправку одного события из шины. Когда вызывается метод Execute()
, он проверяет идентификатор потока. Если он не соответствует идентификатору потока пользовательского интерфейса (установленному во время создания), в куче выделяется функция DeferredFunction с экземпляром, методом и аргументом события. Указатель на него отправляется в поток пользовательского интерфейса через PostThreadMessage()
.
Наконец, хук-функция для потока сообщений потока используется для вызова функции DeferredFunction и отмены ее выделения. В качестве альтернативы я могу использовать фильтр цикла сообщений, так как мой UI Framework (WTL) поддерживает их.
В конце концов, это хорошая идея? Вся эта перехват сообщения заставляет меня злиться. Намерение, конечно, благородное, но есть ли какие-нибудь подводные камни, о которых я должен знать? Или есть более простой способ сделать это?