#multithreading #delphi #vcl
#многопоточность #delphi #vcl
Вопрос:
Я разрабатываю приложение с многопоточностью (RAD Studio XE5). При запуске приложения я создаю один поток, который будет существовать столько же, сколько и основная форма.
Я могу отправлять сообщения из потока в любую форму, созданную в моем приложении, однако я не могу найти способ сделать обратное, отправив сообщение из основного потока VCL в рабочий поток.
При создании основной формы я создаю рабочий поток и копирую дескриптор в общедоступную переменную:
serverThread := TMyThread.Create(True, ServerPort 1);
serverThreadHandle := serverThread.Handle; // SAVE HANDLE
serverThread.Start;
затем (из другой формы FrmSender) Я отправляю сообщение в поток:
PostMessage(uMain.serverThreadHandle, UM_LOC_VCLMSG, UM_LOC_VCLMSG, Integer(PStrListVar));
Это процедура выполнения потока:
procedure TMyThread.Execute;
var
(..)
vclMSG : TMsg;
str1, str2 : string;
(..)
begin
while not(Terminated) do
begin
Sleep(10);
if Assigned(FrmSender) then
if FrmSender.HandleAllocated then
if PeekMessage(vclMSG, FrmSender.Handle, 0, 0, PM_NOREMOVE) then
begin
if vclMSG.message = UM_LOC_VCLMSG then
begin
try
pStrListVar := pStrList(vclMSG.lParam);
str1 := pStrListVar^.Strings[0];
str2 := pStrListVar^.Strings[1];
finally
Dispose(pStrListVar);
end;
end;
end;
(.. do other stuff ..)
end;
end;
Однако PeekMessage() никогда не возвращает true, как если бы он никогда не получал никаких сообщений. Я попытался изменить параметры на PeekMessage():
PeekMessage(vclMSG, 0, 0, 0, PM_NOREMOVE);
Но безрезультатно.
Есть идеи?
Комментарии:
1. Вы не отправляете сообщение в дескриптор потока, чтение документации для postMessage поможет.
2. Забыл упомянуть, что поток является производным от класса TThread
Ответ №1:
Из документации по функциям MSDN PostMessage
:
Помещает (отправляет) сообщение в очередь сообщений, связанную с потоком, создавшим указанное окно, и возвращает, не дожидаясь, пока поток обработает сообщение.
Чтобы опубликовать сообщение в очереди сообщений, связанной с потоком, используйте функцию PostThreadMessage .
Таким образом, вы должны использовать PostThreadMessage
:
Отправляет сообщение в очередь сообщений указанного потока. Он возвращается, не дожидаясь, пока поток обработает сообщение.
Обратите особое внимание на раздел Замечаний. Потоку получателя требуется очередь сообщений. Принудительно запустите поток, имеющий один, выполнив следующие действия:
- Создайте объект события, затем создайте поток.
- Используйте
WaitForSingleObject
функцию, чтобы дождаться, пока событие будет установлено в сигнальное состояние перед вызовомPostThreadMessage
. -
В потоке, в который будет отправлено сообщение, вызовите
PeekMessage
, как показано здесь, чтобы заставить систему создать очередь сообщений.PeekMessage(amp;msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)
-
Установите событие, чтобы указать, что
поток готов к приему отправленных сообщений.
Затем, при использовании PeekMessage
, вы передаете значение дескриптора -1
функции, как задокументировано.
Ответ №2:
PeekMessage(vclMSG, FrmSender.Handle, 0, 0, PM_NOREMOVE)
Второй аргумент означает, что вы будете получать только сообщения, отправленные отправителю, FrmSender.Handle
. Но вы отправили сообщения получателю, uMain.serverThreadHandle
. Это одна из причин PeekMessage
, по которой никогда не может вернуться.
Неправильный доступ к VCL из потока, как вы это делаете. Дескриптор формы подлежит повторному созданию окна VCL, и существует явная гонка на HandleAllocated
и Handle
. Поэтому, даже если вам нужно было знать FrmSender.Handle
, было бы неправильно запрашивать это в потоке.
На самом деле вы отправляете сообщение в дескриптор потока, а не в дескриптор окна. Это означает, что сообщение даже не отправляется, еще одна причина PeekMessage
, по которой невозможно вернуть. Если бы вы проверили возвращаемое значение при вызове PostMessage
, вы бы это узнали.
Я бы использовал либо PostThreadMessage
, либо отправил сообщение в окно, выделенное в потоке, с вызовом AllocateHWnd
.
Как только вам удастся фактически отправить сообщение, ваше использование PM_NOREMOVE
означает, что очередь сообщений никогда не будет очищена.
Ваше использование Sleep
выглядит для меня очень сомнительным. Почему бы не использовать GetMessage
и так блокировать, пока не поступит сообщение. Каждый раз, когда вы видите вызов Sleep
, будьте очень подозрительны.
Ваше приведение к Integer
приведет к усечению указателя в 64-разрядной сборке. Правильный тип для приведения — это LPARAM
.
Я ожидаю, что есть и другие ошибки, это только те, которые я смог увидеть при быстром 2-минутном сканировании.
Комментарии:
1. Не знал о проблеме с приведением Integer() в сборках x64. Спасибо!
2. Вам действительно нужно исправить и все остальные проблемы
3. Мало того, что код подвержен повторной загрузке окна, но и другая проблема заключается в том, что один поток просто не может получать сообщения для окна, принадлежащего другому потоку (если вы не используете перехват сообщений через
SetWindowsHookEx()
). Только поток, создающий окно, может получать сообщения для этого окна. Также обратите внимание, чтоAllocateHWnd()
это не является потокобезопасным и не должно использоваться вне основного потока.4. Я всегда забываю об этом, потому что я использую потокобезопасную версию
5. На самом деле в моем вопросе есть ошибка, потому что у меня создалось впечатление, что вторым параметром PeekMessage() был дескриптор формы, из которой поступало сообщение. Теперь я передаю 0 и получаю любое сообщение в поток, поскольку у него нет собственных форм.