delphi 如何从匿名线程中获取结果?

cygmwpex  于 2023-04-11  发布在  其他
关注(0)|答案(2)|浏览(325)

基本上,我需要做的是:
在主表单的顶部向用户显示一个“Please wait ...”表单(我们称之为waitForm),执行http方法(get和post),并在获得http响应后关闭waitForm
由于http post方法与物理设备通信,因此返回响应需要一段时间(这就是为什么我在工作线程中执行此操作,因此主线程不会出现“未响应”)。
我已经将http超时设置为1分钟,并尝试使用匿名线程来执行http方法,以便应用程序不会进入“无响应”状态,到目前为止,它的工作符合预期。
这里我的问题是,我需要在线程完成后在应用程序中进一步使用该响应字符串。这是我到目前为止的代码:

function TForm1.test: string;
var
  ReqStream, ResStream: TStringStream;
  http: TIdhttp;
  command, commandName: string;
begin
  TThread.CreateAnonymousThread(
    procedure
    begin
      TThread.Synchronize(nil,
        procedure
        begin
          waitForm.Label1.Caption := 'Please wait ...';
          waitForm.BitBtn1.Enabled := False;
          waitForm.FormStyle := fsStayOnTop;
          waitForm.Show;
        end);
      try
        http := TIdHTTP.Create(nil);
        ReqStream := TStringStream.Create('', TEncoding.UTF8);
        ResStream := TStringStream.Create('', TEncoding.UTF8);
        try
          command := '{"Amount":"69.00","TerminalName":"SIMULATE","omitSlipPrintingOnEFT":"1"}';
          commandName := 'sale';
          http.Request.ContentType := 'application/json';
          http.Request.CharSet := 'utf-8';
          http.Request.ContentEncoding := 'utf-8';
          http.Request.Accept := 'application/json';
          http.Request.Connection := 'keep-alive';
          http.ConnectTimeout := 60000;
          http.ReadTimeout := 60000;
          ReqStream.WriteString(command);
          ReqStream.Position := 0;
          try
            http.Post('http://localhost:5555/' + CommandName, ReqStream, ResStream);
            http.Disconnect;
            self.result := ResStream.DataString; // I know this can't be written like that
          except
            //
          end;
        finally
          FreeAndNil(ReqStream);
          FreeAndNil(http);
        end;
      finally
        TThread.Synchronize(nil,
        procedure
        begin
          waitForm.FormStyle := fsNormal;
          waitForm.BitBtn1.Enabled := True;
          waitForm.Close;
        end);
      end;
    end).Start;
ShowMessage(result); // this is where I call next procedure to parse the response.
end;

在我们从test()函数得到结果之后,我需要解析它并在应用程序中进一步使用它。fsStayOnTop和禁用按钮是我发现的阻止用户与主窗体交互的唯一解决方案,因为.ShowModal不是一个选项,因为它阻止了即使使用Synchronize()也无法继续执行函数(我错了吗?)。
我也试过使用ITaskIFuture<string>,但似乎不能让它像预期的那样工作。也许我应该使用匿名函数。

lf3rwulv

lf3rwulv1#

如果你想让TForm1.test()在内部使用工作线程的同时 * 同步 * 工作,你必须等待工作线程完成,然后才能退出函数。这也允许你的匿名过程捕获一个局部变量,然后你可以在线程完成后将该变量赋值给函数的Result
试试类似这样的东西:

function TForm1.test: string;
var
  myThread: TThread;
  response: string;
begin
  waitForm.Label1.Caption := 'Please wait ...';
  waitForm.BitBtn1.Enabled := False;
  waitForm.FormStyle := fsStayOnTop;
  waitForm.Show;

  try
    myThread := TThread.CreateAnonymousThread(
      procedure
      var
        http: TIdHTTP;
        ReqStream: TStringStream;
        command, commandName: string;
      begin
        command := '{"Amount":"69.00","TerminalName":"SIMULATE","omitSlipPrintingOnEFT":"1"}';
        commandName := 'sale';
        http := TIdHTTP.Create(nil);
        try
          http.Request.ContentType := 'application/json';
          http.Request.CharSet := 'utf-8';
          http.Request.Accept := 'application/json';
          http.Request.Connection := 'close';
          http.ConnectTimeout := 60000;
          http.ReadTimeout := 60000;
          ReqStream := TStringStream.Create(command, TEncoding.UTF8);
          try
            response := http.Post('http://localhost:5555/' + CommandName, ReqStream);
          finally
            ReqStream.Free;
          end;
        finally
          http.Free;
        end;
      end
    );
    try
      myThread.FreeOnTerminate := False;
      myThread.Start;

      myThread.WaitFor;
      { alternatively...
      var H: array[0..1] of THandle;
      H[0] := myThread.Handle;
      H[1] := Classes.SyncEvent;
      repeat
        case MsgWaitForMultipleObjects(2, H, False, INFINITE, QS_ALLINPUT) of
          WAIT_OBJECT_0 + 0: Break;
          WAIT_OBJECT_0 + 1: CheckSynchronize;
          WAIT_OBJECT_0 + 2: Application.ProcessMessages;
          WAIT_FAILED      : RaiseLastOSError;
        end;
      until False;
      }
    finally
      if myThread.FatalException <> nil then
      begin
        //...
      end;
      myThread.Free;
    end;
  finally
    waitForm.FormStyle := fsNormal;
    waitForm.BitBtn1.Enabled := True;
    waitForm.Close;
  end;

  Result := response;
  ShowMessage(Result); // this is where I call next procedure to parse the response.
end

否则,你应该打破你的逻辑,让TForm1.test()以异步方式工作,并让它在线程完成并且响应可用时通知你的代码,例如:

procedure TForm1.do_test;
var
  myThread: TThread:
begin
  myThread := TThread.CreateAnonymousThread(
    procedure
    var
      http: TIdHTTP;
      ReqStream: TStringStream;
      command, commandName, response: string;
    begin
      command := '{"Amount":"69.00","TerminalName":"SIMULATE","omitSlipPrintingOnEFT":"1"}';
      commandName := 'sale';
      http := TIdHTTP.Create(nil);
      try
        http.Request.ContentType := 'application/json';
        http.Request.CharSet := 'utf-8';
        http.Request.Accept := 'application/json';
        http.Request.Connection := 'close';
        http.ConnectTimeout := 60000;
        http.ReadTimeout := 60000;
        ReqStream := TStringStream.Create(command, TEncoding.UTF8);
        try
          response := http.Post('http://localhost:5555/' + CommandName, ReqStream);
        finally
          ReqStream.Free;
        end;
      finally
        http.Free;
      end;
      TThread.Queue(nil,
        procedure
        begin
          Self.HandleResponse(response);
        end);
      end;
    end
  );
  myThread.OnTerminate := ThreadTerminated;

  waitForm.Label1.Caption := 'Please wait ...';
  waitForm.BitBtn1.Enabled := False;
  waitForm.FormStyle := fsStayOnTop;
  waitForm.Show;

  try
    myThread.Start;
  except
    ThreadTerminated(nil);
    raise;
  end;
end;

procedure TForm1.ThreadTerminated(Sender: TObject);
begin
  waitForm.FormStyle := fsNormal;
  waitForm.BitBtn1.Enabled := True;
  waitForm.Close;

  if (Sender <> nil) and (TThread(Sender).FatalException <> nil) then
  begin
    //...
  end;
end;

procedure TForm1.HandleResponse(const Response: string);
begin
  ShowMessage(Response); // this is where I call next procedure to parse the response.
end;

话虽如此
我是在一个辅助线程中执行此操作的,因此主线程不会显示为“无响应”
虽然这总体上是个好主意,但我只想指出Indy确实有一个TIdAntiFreeze组件来解决这个问题。您可以让test()函数在不使用工作线程的情况下 * 同步 * 工作,让TIdAntiFreezeTIdHTTP阻塞主线程的同时抽取主消息队列。
例如:

function TForm1.test: string;
var
  http: TIdHTTP;
  ReqStream: TStringStream;
  command, commandName: string;
begin
  command := '{"Amount":"69.00","TerminalName":"SIMULATE","omitSlipPrintingOnEFT":"1"}';
  commandName := 'sale';

  waitForm.Label1.Caption := 'Please wait ...';
  waitForm.BitBtn1.Enabled := False;
  waitForm.FormStyle := fsStayOnTop;
  waitForm.Show;

  // place a TIdAntiFreeze onto your Form, or
  // create it dynamically here, either way will work...

  try
    http := TIdHTTP.Create(nil);
    try
      http.Request.ContentType := 'application/json';
      http.Request.CharSet := 'utf-8';
      http.Request.Accept := 'application/json';
      http.Request.Connection := 'close';
      http.ConnectTimeout := 60000;
      http.ReadTimeout := 60000;
      ReqStream := TStringStream.Create(command, TEncoding.UTF8);
      try
        Result := http.Post('http://localhost:5555/' + CommandName, ReqStream);
      finally
        ReqStream.Free;
      end;
    finally
      http.Free;
    end;
  finally
    waitForm.FormStyle := fsNormal;
    waitForm.BitBtn1.Enabled := True;
    waitForm.Close;
  end;

  ShowMessage(Result); // this is where I call next procedure to parse the response.
end;
aiazj4mn

aiazj4mn2#

您可以在回调中检索线程执行结果:

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    Thread: TThread;
    ThreadResult: string;
    procedure OnThreadTerminate(Sender: TObject);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  Waits;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Thread := TThread.CreateAnonymousThread(procedure
  var HttpResult: string;
  begin
    TThread.Synchronize(nil, procedure
    begin
      WaitForm.Show;
    end);
    try
      Sleep(4000); // Here do Your http request
      // raise Exception.Create('Http request error');
      HttpResult := 'Result from thread';
    except
      HttpResult := 'Something bad happened';
    end;
    TThread.Synchronize(nil, procedure
    begin
      ThreadResult := HttpResult;
      WaitForm.Close;
    end);
  end);
  Thread.OnTerminate := OnThreadTerminate;
  Thread.Start;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Thread.Free;
end;

procedure TForm1.OnThreadTerminate(Sender: TObject);
begin
  Thread := nil;
  ShowMessage(ThreadResult);
end;

注意,把整个线程执行代码放在TThread.Synchronize中是没有意义的。同步执行主线程中的代码,所以它将像没有线程一样工作。
这只是一个工作证明,我不确定我会在真实的的场景中使用这个设计。

相关问题