Есть два основных метода, которыми я чаще всего общаюсь между потоками.
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) потоками, и это часто является наиболее эффективным способом сделать это.