Выполнение метода в потоке пользовательского интерфейса из-за события в фоновом потоке - PullRequest
3 голосов
/ 24 сентября 2010

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

Как получить конкретный метод static void DataHandler(void* data) для выполнения в потоке пользовательского интерфейса?

Я думаю, что создание таймера, передающего hwnd и указатель на функцию, будет работать. Но есть ли лучший способ? Могу ли я использовать PostMessage для вызова обработчика данных.

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

Ответы [ 3 ]

25 голосов
/ 24 сентября 2010

Есть два основных метода, которыми я чаще всего общаюсь между потоками.

1) PostMessage ()

Создать собственное сообщение Windows, аля:

#define WM_YOU_HANVE_DATA WM_USER + 101

Создайте пользовательский тип данных, который будет содержать данные, которые вы хотите отправить в главный поток для обработки:

struct MyData
{
  string client_;
  string message_type_;
  string payload_;
};

В рабочем потоке создайте копию MyData в куче , заполните ее и отправьте в основной поток:

MyData* data = new MyData;
data->client_ = "hoser";
// ... etc
PostMessage(main_wnd_handle, WM_YOU_HAVE_DATA, reinterpret_cast<WPARAM>(data), );

В главном потоке обработайте это сообщение и обработайте данные любым подходящим способом.

BEGIN_MESSAGE_MAP(MyAppWindow, CDialogEx)
        // ...  stuff's going to already be here
        ON_MESSAGE(WM_YOU_HAVE_DATA, OnYouHaveData)
END_MESSAGE_MAP()

// ...

Важное примечание: основной поток MyAppWindow теперь владеет памятью, на которую указывает MyData*, поэтому вы должны стать ее владельцем. Я делаю это с auto_ptr здесь:

LRESULT MyAppWindow::OnYouHaveData(WPARAM wp, LPARAM )
{
  auto_ptr<MyData> data(reinterpret_cast<MyData*>(wp));
  DisplayeClient(data->client_);
  // etc
  return 0;  
}

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

Самым большим недостатком этого подхода являются ограничения в масштабе. Это полагается на насос сообщений Windows для перемещения данных между потоками. Почти всегда это не проблема. Но существует ограничение на количество сообщений, которое может обрабатывать очередь сообщений Windows:

Существует ограничение в 10 000 опубликованных сообщений на очередь сообщений.

(* +1036 * ссылка * * тысяча тридцать семь) * * тысяча тридцать восемь

Опять же, для большинства приложений это не проблема.

2) QueueUserAPC ()

Асинхронный вызов процедуры (APC) - это функция, которая выполняется асинхронно в контексте определенного потока. ( Link ) Если есть функция ProcessIncomingData(), которую вы хотите выполнить в главном потоке, но вы хотите запустить ее из рабочего потока, вы можете вызвать эту функцию довольно прямым способом, используя QueueUserAPC().

Как и в случае метода PostMessage(), вы начинаете с пользовательского типа данных, который вы создаете в куче:

struct MyData
{
  string client_;
  string message_type_;
  string payload_;
};

// ...

MyData* data = new MyData;
data->client_ = "hoser";

Определите пользовательский APC, помня взять на себя ответственность за входящие данные :

VOID CALLBACK ProcessIncomingData(ULONG_PTR in)
{
  auto_ptr<MyData> data(reinterpret_cast<MyData*>(in));
  // magic happens
}

Затем вы ставите в очередь вызов асинхронной процедуры. При использовании метода PostMessage() вам понадобилось окно главного потока HWND. Здесь вам нужна актуальная нить основного потока. РУЧКА.

HANDLE main_thread = my_thread_params.main_thread_handle_;
QueueUserAPC(ProcessIncomingData, main_thread, reinterpret_cast<ULONG_PTR>(data));

Есть одна БОЛЬШАЯ оговорка. Чтобы ваш APC вызывался основным потоком, основной поток должен находиться в состоянии ожидания с предупреждением . Вы вводите состояние ожидания с оповещением при вызове одной из функций WaitEx (), таких как WaitForMultipleObjectsEx () с флагом «alerttable», установленным в true.

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

0 голосов
/ 24 сентября 2010

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

После этого просто инкапсулироватьпроизвольный вызов функции (я использую динамически размещенный объект boost :: function, но есть и другие варианты), передаю его потоку главного окна с пользовательским идентификатором сообщения, и обработчик должен вызываться в контексте основного потока. Создание пользовательских типов сообщений в win32? содержит подробности о пользовательской части сообщения, как вы заметили;просто убедитесь, что любые передаваемые вами данные / объект размещены в куче и освобождены внутри функции-обработчика, если это будет асинхронный вызов (например, PostMessage).

Обновление: что сказал Джон в опции 1, кроменаписать операцию обработчика в функции, которая вызывается обработчиком в пользовательском интерфейсе, чтобы вы имели минимальное влияние на код пользовательского интерфейса, и если вы изменяете структуру данных, которую вы передаете, вам не нужно обновлять пользовательский интерфейскод.Это мое единственное дополнительное предложение.

Надеюсь, это поможет.

0 голосов
/ 24 сентября 2010

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

Теперь, когда я перечитал ваш вопрос - поскольку вы не можете изменить код пользовательского интерфейса, этот подход не сработает. Вы можете использовать WIN32 API для добавления своей собственной функции перехвата сообщений, чтобы решить эту проблему.

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