使用TrestRequest执行 Delphi 线程

7cjasjjr  于 2023-08-04  发布在  其他
关注(0)|答案(2)|浏览(201)

我对RestRequest工具比较陌生。我希望有人已经解决了等待异步调用完成的问题。
我有一个程序,它会进行数百个不同的API调用,然后调用处理结果,并将结果传递回调用api执行的过程,然后它可以继续…
唯一的问题,总是做这样的调用没有线程,是软件挂起,直到调用完成。为了尝试补救这个问题,我将RESTRequest.Execute;更改为RESTRequest.ExecuteAsync();,但现在我的问题是代码继续运行,而没有等待restrequest的响应。
再次尝试绕过这个问题,我已经尝试了几个解决方案,甚至api.RESTRequest.ExecuteAsync().WaitFor;(抛出错误thread error: the handler is invalid (6)
有没有任何方法可以改变下面的函数作为一个单独的线程运行(只有执行部分是重要的线程运行)...基本上,我只是想在每次调用函数时显示一个动画加载图标,并为我的代码的其余部分直到这个函数完成。
我希望有一个更简单的解决方案,而不是开始在多线程上使用full。

编码

function run_api_command():boolean;
begin

  result := false;
  RESTResponse.Content.Empty;
  RESTAdapter.Dataset:= ds_action;
  RESTAdapter.RootElement:= '';

  try
    RESTRequest.ExecuteAsync;

    if(ds_action.Active = false) then ds_action.Active:=true;
    if(ds_action.FieldByName('result').AsString<>'Success') then
      begin
        showmessage(ds_action.FieldByName('result').AsString);
      end
    else
      begin
        RESTAdapter.RootElement:= 'data';
        result := true;
      end;
  except
    on E: Exception do
      begin
        if(e.Message = 'REST request failed: Error sending data: (12007) The server name or address could not be resolved') then
          begin
            if(messagedlg('Could not connect to server. Would you like to retry?', mterror,[mbYes,mbNo],0)=mrYes) then
              begin
                result := run_api_command();
              end;
          end
        else
          begin
            showmessage(RESTResponse.Content);
          end;
      end;
  end;
end;

字符串

pprl5pva

pprl5pva1#

ExecuteAsync()运行在工作线程(它返回的TRESTExecutionThread对象)中。但是,ExecuteAsync()有一个AFreeThread参数,默认为True。正如documentation明确指出的那样:
AFreeThread参数设置为False时,此方法将向执行线程返回一个reference

注意:如果AFreeThread参数设置为True,则该方法返回无效引用。

所以在返回的对象指针上调用WaitFor()默认情况下会崩溃。
即使返回的对象指针在AFreeThread=True时有效,调用WaitFor()仍然会崩溃。当TThread对象的FreeOnTerminate属性被设置为True时,该对象在线程运行结束时释放其底层API句柄(以及从内存中销毁自身),这导致WaitFor()失败并出现“句柄无效”错误(甚至导致访问违规或类似错误)。TThread.WaitFor()有一个逻辑错误1,它不能处理TThread.FreeOnTerminate=True时的情况。
(1这个bug是在 Delphi 6中引入的,当时TThread被重写以支持Kylix,并且在以后的版本中从未被纠正。
如果希望调用方等待REST服务器的响应,请执行以下操作:

  • 使用Execute()而不是ExecuteAsync(),或者至少设置AFreeThread=False,这样你就可以在线程对象上调用WaitFor()(或等效的,比如MsgWaitForMultipleObjects()),然后处理在主UI线程中执行等待的后果。
  • 使用Execute(),但将整个逻辑移动到自己的工作线程,并根据需要与主UI线程同步。

更好的解决方案是根本不等待,这意味着重新设计代码流。继续使用ExecuteAsync(),但传递一个完成回调,当请求完成时将调用该回调,并让该回调驱动代码的下一步。不要主动等待ExecuteAsync()完成,让它通知您。

procedure run_api_command();
begin
  ...
  RESTRequest.ExecuteAsync(
    procedure
    begin
      if not RESTResponse.Status.Success then
      begin
        // do something ...
      end else begin
        // do something else ...
      end;
    end,
    True
  );
end;

字符串

lawou6xi

lawou6xi2#

从雷米的回答来看...
“但现在我的问题是我的代码不等待restrequest的响应就继续”
这就是异步请求的确切目的。你不应该等待,你应该发送请求,然后继续做别的事情。如果您希望捕获结果,还应该提供一个回调过程,但我没有看到您在代码中这样做。
同步调用和异步调用是完全不同的设计体系结构,不能像您希望的那样简单地在它们之间切换。您不能只将Execute更改为ExecuteAsync,而不重新设计其余部分。Execute本质上等待调用线程内的响应。但是,ExecuteAsync本质上会在一个新的线程中产生请求,以便您的代码可以在后台继续执行您的工作。在Async请求中不涉及等待--这违背了它们的全部目的。
在您的情况下,最理想的选择是将一个请求与另一个请求的响应链接起来。意思是,只发送您的第一个请求。一旦收到响应,就从其响应回调中发送下一个请求。
......因为我想将函数创建为一种“全局API调用”函数,以使编码少一点......
那很好。您仍然可以有一个执行所有工作的主过程。在这里,重要的是什么时候打电话。不过,按钮的OnClick事件永远不是执行此类操作的适当位置。该按钮唯一应该做的事情(因为它在主UI线程中)就是发起一个请求。如果您希望UI在此之后能够响应,那么该请求应该以某种方式、形状或形式产生一个新的线程。
不过,无论如何,WaitFor都无法满足您的要求。您需要一个响应迅速的UI。WaitFor将无视这个要求,并锁定应用程序的主线程。在另一个线程中执行此工作的全部目的是显示一个“等待”动画,如果主线程忙碌等待任何事情,则不会显示动画。

编辑

我其实想到了另一种可能。仍然只生成您的第一个请求。但是,与其将一个请求与另一个请求的响应“链接”在一起,不如在第一个请求的回调中完成所有其余的工作。由于此时它已经在一个线程中,因此不一定需要为下一个请求生成另一个线程。在第一个请求之后,只需在已经生成的现有线程中执行所需的其余部分。
顺便说一句,这是现代编程世界中一个非常常见的问题...
x1c 0d1x的数据

相关问题