Как поток может уведомить объект, у которого нет дескриптора окна?


Я новичок в многопоточности, но не полный новичок. Мне нужно выполнить вызов веб-службы в рабочем потоке.

В основном потоке у меня есть форма (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 6

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 не является потокобезопасным, как указано здесь.

Альтернативы, основанные на использовании события:

  1. Используйте 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 истинен.

  2. Добавьте новое свойство event в класс thread, в котором вы можете указать флаг в качестве параметра, указывающего завершение / результат потока. Что-то вроде показано здесь. Обязательно синхронизируйте вызов события. Или забудьте о параметре и просто вызовите событие, только если выполнение выполнено изящно (как вы делаете сейчас).