Search code examples
multithreadingdelphithread-safetyvclidhttp

Delphi: how to execute procedure in main thread from idHTTPServer.OnCommandGet method


I have to create an embedded HTTP server in our application. This must be created for interprocess communication, because the other side can call only WebService.

We don't need to process more requests at once, but I must use the libraries and DB connection from the main thread.

The Delphi version is Seattle, the Indy version is 10 (internal).

As I know the IdHTTPServer uses threads for connections - as formerly. But the new event handler don't pass the Thread directly - so I can't call TThread.Synchronize as N years before. And I didn't find any way to get the thread.

Somewhere I've read that TIdSync or TIdNotify classes could help me. I can't find any complete example to see how.

See this simple code. Is it enough to do my work in main thread?

procedure TForm5.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
    AResponseInfo: TIdHTTPResponseInfo);
begin
    FURI := ARequestInfo.URI; // Store the URI for main thread access
    FResult := '';
    TIdSync.SynchronizeMethod(Self.ProcessIt); // Call sync to process in VCL thread
    AResponseInfo.ContentText := FResult; // This variable contains the result
end;

Or I do something wrong?

Thank you for the help!

dd


Solution

  • There is a way to get access to the underlying TThread behind a TIdContext - typecast the TIdContext.Yarn property to TIdYarnOfThread and then access its Thread property, eg:

    procedure TForm5.IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    var
      Thread: TThread;
    begin
      Thread := (AContext.Yarn as TIdYarnOfThread).Thread;
      FURI := ARequestInfo.URI;
      FResult := '';
      Thread.Synchronize(ProcessIt);
      AResponseInfo.ContentText := FResult;
    end;
    

    But, you don't actually need that in your situation, because TThread.Synchronize() (and TThread.Queue()) has class method overloads, so you don't need a TThread object to call it, eg:

    procedure TForm5.IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    begin
      FURI := ARequestInfo.URI;
      FResult := '';
      TThread.Synchronize(nil, ProcessIt);
      AResponseInfo.ContentText := FResult;
    end;
    

    TIdSync (and TIdNotify) can indeed be used instead, and what you have is a good start for that, it just needs to be fleshed out a little more so that access to your FURI and FResult variables is synchronized as well, in case you ever need to handle more than 1 client connection at a time and thus would need to avoid concurrent access to the variables, eg:

    type
      TIdProcessItSync = class(TIdSync)
        URI: string;
        Content: string;
      protected
        procedure DoSynchronize; override;
      end;
    
    procedure TIdProcessItSync.DoSynchronize;
    begin
      Content := Form5.ProcessIt(URI);
    end;
    
    function TForm5.ProcessIt(const URI: string): String;
    begin
      Result := ...;
    end;
    
    procedure TForm5.IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    var
      Sync: TIdProcessItSync;
    begin
      Sync := TIdProcessItSync.Create;
      try
        Sync.URI := ARequestInfo.URI;
        Sync.Synchronize;
        AResponseInfo.ContentText := Sync.Content;
      finally
        Sync.Free;
      end;
    end;
    

    That being said, TIdSync (and TIdNotify) is deprecated in favor of TThread's class method overloads that support anonymous procedures, eg:

    function TForm5.ProcessIt(const URI: string): String;
    begin
      Result := ...;
    end;
    
    procedure TForm5.IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    var
      URI, Content: String;
    begin
      URI := ARequestInfo.URI;
      TThread.Synchronize(nil, 
        procedure
        begin
          Content := ProcessIt(URI);
        end
      );
      AResponseInfo.ContentText := Content;
    end;
    

    Alternatively:

    function TForm5.ProcessIt(const URI: string): String;
    begin
      Result := ...;
    end;
    
    procedure TForm5.IdHTTPServer1CommandGet(AContext: TIdContext;
      ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
    begin
      TThread.Synchronize(nil, 
        procedure
        begin
          AResponseInfo.ContentText := ProcessIt(ARequestInfo.URI);
        end
      );
    end;