使用TRestRequest进行Delphi线程处理

时间:2017-09-05 21:34:56

标签: multithreading delphi

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

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

只有在没有线程的情况下进行此类调用时才会出现问题,即软件会一直挂起,直到通话完成为止...为了尝试解决此问题,我将RESTRequest.Execute;更改为RESTRequest.ExecuteAsync();,但现在我的问题是我的代码继续而不等待restrequest的响应。

再次试图绕过这个问题我尝试了几种解决方案,甚至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 个答案:

答案 0 :(得分:8)

ExecuteAsync()在工作线程(它返回的TRESTExecutionThread对象)中运行。但是,ExecuteAsync()的{​​{1}}参数默认为AFreeThread。正如documentation明确指出:

  

True参数设置为False时,此方法会向此执行线程返回reference

     

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

因此,默认情况下,对返回的对象指针调用AFreeThread将会崩溃。

即使返回的对象指针在WaitFor()时有效,调用AFreeThread=True仍然会崩溃。当WaitFor()对象的TThread属性设置为FreeOnTerminate时,该对象在线程终止时释放其底层API句柄,这会导致True失败并且“句柄无效“错误。 WaitFor()有一个逻辑错误 1 TThread.WaitFor()时没有处理这种情况。

1 这个错误一直在Delphi 6中引入,当TThread.FreeOnTerminate=True被重写为Kylix支持时,它在以后的版本中从未得到纠正。)

如果您希望调用者等待来自REST服务器的响应,请执行以下操作:

  • 使用TThread代替Execute(),或者至少设置ExecuteAsync(),以便您可以拨打AFreeThread=False(或等同于WaitFor())线程对象,然后处理在主UI线程中执行等待的后果:

  • 使用MsgWaitForMultipleObjects(),但将整个逻辑移动到您自己的工作线程,并根据需要与主UI线程同步。

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

ExecuteAsync

答案 1 :(得分:5)

延伸到雷米的回答......

  

“但现在我的问题是我的代码继续而不等待restrequest的响应”

这是异步请求的确切目的。你不应该等待,你应该发送请求并转向别的东西。如果你想捕获结果,你也应该提供一个回调程序,但我没有看到你在代码中这样做。

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

在您的情况下,最理想的选择是将一个请求链接到另一个请求。含义,仅发送您的第一个请求。收到响应后,从响应回调中发送下一个请求。

  

“...因为我想将函数创建为一种”全局api调用“函数,以使编码更少......”

这很好。您仍然可以使用一个主要过程来执行所有工作。是什么时候让这些呼叫在这里重要。但是,按钮的OnClick事件从来不是做这种事情的合适场所。按钮应该做的唯一事情(因为它在主UI线程中)是发起请求。如果您希望UI在此之后完全响应,那么该请求应该以某种方式,形状或形式生成新线程。

在任何情况下,WaitFor都不会根据您的要求削减它。您想要一个响应式UI。 WaitFor将违反此要求,并且无论如何都会锁定应用程序的主线程。在另一个线程中执行此工作的全部目的是显示“等待”动画,如果主线程忙于等待任何事情,则不会对其进行动画处理。

修改

我实际上想到了另一种可能性。仍然只产生你的第一个请求。但是,不是将一个请求“链接”到另一个响应中,而是在第一个请求的回调中完成所有其余工作。从那时起,它已经在一个线程中,不一定需要为下一个请求产生另一个线程。只需在第一个请求之后执行您已经生成的现有线程中剩余的所需内容。

另一方面,这是现代编程世界中一个非常普遍的问题......

I Hate Programming