Как поток может уведомить объект, у которого нет дескриптора окна?
Я новичок в многопоточности, но не полный новичок. Мне нужно выполнить вызов веб-службы в рабочем потоке.
В основном потоке у меня есть форма (TForm) с частным элементом данных (private string), в который будет записываться только рабочий поток (я передаю указатель a в поток, прежде чем он возобновится). Когда рабочий поток завершает вызов webservice и записывает результирующий ответ xml в закрытый элемент формы, рабочий поток использует PostMessage для отправки сообщение для дескриптора формы (которое я также передал в поток, прежде чем он возобновился).
interface
const WM_WEBSERVCALL_COMPLETE = WM_USER + 1;
type
TWebServiceResponseXML = string;
PWebServiceResponseXML = ^TWebServiceResponseXML;
TMyForm = class(TForm)
...
private
...
fWorkerThreadID: Cardinal;
fWebServiceResponseXML: TWebServiceResponseXML;
public
...
procedure StartWorkerThread;
procedure OnWebServiceCallComplete(var Message: TMessage); Message WM_WEBSERVCALL_COMPLETE;
end;
TMyThread = class(TThread)
private
protected
procedure Execute; override;
public
SenderHandle: HWnd;
RequestXML: string;
ResponseXML: string;
IMyService: IService;
PResponseXML: PWebServiceResponseXML;
end;
implementation
procedure TMyForm.StartWorkerThread;
var
MyWorkerThread: TMyThread;
begin
MyWorkerThread := TMyThread.Create(True);
MyWorkerThread.FreeOnTerminate := True;
MyWorkerThread.SenderHandle := self.Handle;
MyWorkerThread.RequestXML := ComposeRequestXML;
MyWorkerThread.PResponseXML := ^fWebServiceResponseXML;
MyWorkerThread.Resume;
end;
procedure TMyForm.OnWebServiceCallComplete(var Message: TMessage);
begin
// Do what you want with the response xml string in fWebServiceResponseXML
end;
procedure TMyThread.Execute;
begin
inherited;
CoInitialize(nil);
try
IMyService := IService.GetMyService(URI);
ResponseXML := IMyService.Search(RequestXML);
PResponseXML := ResponseXML;
PostMessage(SenderHandle, WM_WEBSERVCALL_COMPLETE, 0, 0);
finally
CoUninitialize;
end;
end;
Это отлично работает, но теперь я хочу сделать то же самое из datamodule (который не имеет дескриптора)... так что я был бы очень признателен за полезный код, чтобы дополнить рабочую модель, которую я имею.
EDIT
Что мне действительно нужно, так это код (если возможно), который позволил бы мне заменить строку
MyWorkerThread.SenderHandle := self.Handle;
С
MyWorkerThread.SenderHandle := GetHandleForThisSOAPDataModule;
3 ответа:
Я использовал этот метод раньше с некоторым успехом: отправка сообщений в неоконные приложения
В основном, используйте второй поток в качестве насоса сообщений на дескрипторе, полученном через AllocateHWND. Это, безусловно, раздражает, и вам было бы лучше использовать библиотеку, чтобы справиться со всеми деталями. Я предпочитаю OmniThreadLibrary , но есть и другие - см. Как я выбираю между различными способами выполнения потоковой обработки в Delphi? и Delphi-Threading рамки .
Вы можете выделить свой собственный дескриптор с помощью AllocateHwnd и использовать его в качестве цели PostMessage.
TTestThread = class(TThread) private FSignalShutdown: boolean; // hidden window handle FWinHandle: HWND; protected procedure Execute; override; // our window procedure procedure WndProc(var msg: TMessage); public constructor Create; destructor Destroy; override; procedure PrintMsg; end; constructor TTestThread.Create; begin FSignalShutdown := False; // create the hidden window, store it's // handle and change the default window // procedure provided by Windows with our // window procedure FWinHandle := AllocateHWND(WndProc); inherited Create(False); end; destructor TTestThread.Destroy; begin // destroy the hidden window and free up memory DeallocateHWnd(FWinHandle); inherited; end; procedure TTestThread.WndProc(var msg: TMessage); begin if Msg.Msg = WM_SHUTDOWN_THREADS then // if the message id is WM_SHUTDOWN_THREADS // do our own processing FSignalShutdown := True else // for all other messages call // the default window procedure Msg.Result := DefWindowProc(FWinHandle, Msg.Msg, Msg.wParam, Msg.lParam); end;
Вы можете применить это к чему угодно, а не только к потокам. Просто имейте в виду, что AllocateHWND не является потокобезопасным, как указано здесь.
Альтернативы, основанные на использовании события:
Используйте OnTerminate нити (уже присутствующей) в сочетании с флагом:
TMyDataModule = class(TDataModule) private procedure OnWebServiceCallComplete(Sender: TObject); ... TMyThread = class(TThread) public property TerminateFlag: Integer ... ... procedure TMyDataModule.StartWorkerThread; ... MyWorkerThread.OnTerminate := <Self.>OnWebServiceCallComplete; ... procedure TMyDataModule.OnWebServiceCallComplete(Sender: TObject); begin if MyWorkerThread.TerminateFlag = WEBCALL_COMPLETE then ... end;
Задайте TerminateFlag в подпрограмме Execute. OnTerminate автоматически выстрелит, даже если FreeOnTerminate истинен.
Добавьте новое свойство event в класс thread, в котором вы можете указать флаг в качестве параметра, указывающего завершение / результат потока. Что-то вроде показано здесь. Обязательно синхронизируйте вызов события. Или забудьте о параметре и просто вызовите событие, только если выполнение выполнено изящно (как вы делаете сейчас).