Delphi 使用 TRestRequest 进行多线程编程

8

我对RestRequest工具相对陌生。希望有人解决了等待异步调用完成的问题...

我有一个程序,它进行了数百次不同的API调用,然后处理结果,并将结果传回调用API执行的过程,然后可以继续执行...

唯一的问题总是在没有线程的情况下进行这种调用时,软件会挂起,直到调用完成... 为了解决这个问题,我将RESTRequest.Execute;改为RESTRequest.ExecuteAsync();,但现在我的问题是我的代码继续执行而不等待REST请求的响应。

为了尝试解决这个问题,我已经尝试了几种解决方案,甚至使用了api.RESTRequest.ExecuteAsync().WaitFor;(抛出一个错误thread error: the handler is invalid (6)

是否有任何方法可以将下面的函数更改为在单独的线程中运行(只有执行部分重要运行在线程中)... 基本上,我只想在每次调用该函数时显示一个动画加载图标,并且让我的其余代码等待此函数完成...

我希望有一个比开始使用完全多线程更简单的解决方案..

代码

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;
2个回答

9

ExecuteAsync() 在一个工作线程中运行(它返回的 TRESTExecutionThread 对象)。然而,ExecuteAsync() 有一个默认为 TrueAFreeThread 参数。正如 文档 明确说明的:

AFreeThread 参数设置为 False 时,该方法返回对此执行线程的 引用

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

因此,默认情况下,在返回的对象指针上调用 WaitFor() 将导致崩溃。

即使在AFreeThread=True时返回的对象指针是有效的,调用WaitFor()仍然会导致崩溃。当一个TThread对象的FreeOnTerminate属性设置为True时,当线程完成运行时,该对象会释放其底层API句柄(以及从内存中销毁自身),这会导致WaitFor()失败并显示“句柄无效”的错误(甚至可能导致访问冲突或类似的错误)。TThread.WaitFor()存在一个逻辑错误1,没有处理TThread.FreeOnTerminate=True的情况。
1这个错误最早出现在Delphi 6中,当时为了支持Kylix而重写了TThread,后续版本中从未修复。)
如果您希望调用者等待来自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;

这个完成回调函数在C++中可以使用吗? - truthseeker
@truthseeker 是的。请注意,回调函数需要使用匿名过程,这在C++中需要一些额外的工作,但是可以实现。详细信息请参阅如何在C++中处理Delphi匿名方法 - Remy Lebeau
我成功地在C++中编写了完成处理程序。在新的RAD Studio中是否也需要在完成处理程序中使用相同的代码?因为有一段时间以来,已经有了一个名为ACompletionHandlerWithError的参数,我成功地使用过它。不确定是否需要检查RESTResponse.Status.Success,并且如果需要,在哪个完成处理程序(普通和错误)中进行检查。 - truthseeker
我正在回答自己的问题。当存在错误完成处理程序时,需要检查RESTResponse.Status。有些错误(例如HTTP 400 Bad Request)不会调用错误完成处理程序。 - truthseeker

5

延伸Remy的回答...

"但现在我的问题是,我的代码继续执行而不等待restrequest的响应"

这正是异步请求的目的。你不应该等待,而应该发送请求并转向其他事情。如果您希望捕获结果,还应提供回调过程,但我没有在您的代码中看到这样做。

同步和异步调用是完全不同的设计架构,不能像您希望的那样轻松切换。您不能只更改Execute为ExecuteAsync而不重新设计其余部分。Execute自然会在调用线程内等待响应。但是,ExecuteAsync自然会在新线程中生成请求,以便在后台执行工作时您的代码可以继续运行。 Async请求中没有等待-这违反了它们的全部目的。

在您的情况下,最理想的选项是将一个请求链接到另一个响应上。也就是说,仅发送您的第一个请求。一旦收到响应,然后从其响应回调中发送下一个请求。

"......因为我想将该函数创建为一种“全局api调用”函数,以使编码变得更加简单......"
没问题。您仍然可以拥有一个执行所有工作的单个主过程。在这里,重要的是何时进行每个调用。按钮的OnClick事件从来不是这样做的合适场所。由于它在主UI线程中,该按钮唯一应该做的事情是发起请求。如果在此之后您希望您的UI具有响应性,则该请求应以某种方式生成新线程。
在任何情况下,WaitFor对于您的请求来说并不足够。您需要一个响应式的UI。WaitFor将违反此要求,并且无论如何都会锁定您的应用程序的主线程。在另一个线程中执行此工作的整个目的是显示“等待”动画,如果您的主线程忙于等待任何内容,则该动画将无法播放。"

编辑

我实际上想到了另一种可能性。仍然只生成你的第一个请求。但是,不要像将一个请求链接到另一个响应那样,只需从第一个请求的回调函数中完成所有其他工作。由于此时已经在一个线程中,因此没有必要为下一个请求再生成另一个线程。只需在您已经生成的现有线程中执行第一个请求后所需的其余部分即可。


在旁边注意到,这是现代编程世界中非常普遍的问题...

I Hate Programming


@John 我能理解你的困惑 - 我所说的“盲目”只是指不立即观察结果。相反,新线程就像另一个“人”一样,“通知”您“线程”中的“更改”...换句话说,另一个线程负责工作,调用线程不必过多关注具体细节。他们只想要结果。将产生新线程视为雇佣一名可以为您执行工作的新员工。您给他们一个任务,然后让他们去完成这个任务,而您则继续处理其他事情。 - Jerry Dodge
1
我知道 "blindly" 的意思,但在这里使用它是不恰当的。尽管如此,我仍然支持你的回答。 - John Easley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接