2017-09-05 162 views
6

我对RestRequest工具相对较新。我希望有人已经解决了等待异步调用完成的问题...德尔福线程与TRestRequest

我有一个程序,使数百个不同的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; 

回答

7

ExecuteAsync()在辅助线程运行(在TRESTExecutionThread对象,它返回)。但是,ExecuteAsync()AFreeThread参数默认为True。由于documentation明确规定:

AFreeThread参数设置为False,该方法返回一个reference这个执行线程。

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

因此,在返回的对象指针上调用WaitFor()默认会崩溃。

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

(该错误是在Delphi 6中引入的,当时TThread被重写为支持Kylix,并且在以后的版本中从未纠正。)

如果你想在主叫方等待来自REST服务器的响应,则:

  • 使用Execute()代替ExecuteAsync(),或至少设置AFreeThread=False这样你就可以打电话WaitFor()(或同等学历,像MsgWaitForMultipleObjects())线程对象,然后在主UI线程执行等待的后果处理:

  • 使用Execute(),但移动你的整个逻辑到自己的工作线程,并将它与主界面同步线程为需要。

更好的解决方案是根本不用等待,这意味着重新设计代码流。继续使用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; 
5

扩展到雷米的回答......

“但现在我的问题是,我的代码继续,而无需等待restrequest的回应”

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

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

你的情况中最理想的选择是将一个请求链接到另一个请求的响应。意思是,只发送你的第一个请求。一旦你收到回应,然后发送你的下一个请求,在它的响应回调中。

“......因为我想创建功能看作是一种‘全球API调用’功能,使编码少一点......”

这是完全正常的。您仍然可以拥有执行所有工作的单个主程序。是时候做这些重要的电话了。不过,按钮的OnClick事件永远不适合做这样的事情。该按钮应该做的唯一事情(因为它在主UI线程中)是发起一个请求。如果你希望你的用户界面在这一点之后有所反应,那么这个请求应该以某种方式,形状或形式产生一个新的线程。

无论如何,WaitFor只是不会削减它的请求。你想要一个响应式用户界面WaitFor将违反此要求,并锁定您的应用程序的主线程。在另一个线程中完成这项工作的全部目的是显示一个“等待”动画,如果您的主线程正忙于等待任何动画,则该动画将不会生成动画。


编辑

我居然想到的另一种可能性。仍然只产生你的第一个请求。但是,不要从另一个响应中“链接”一个请求,只需在第一个请求的回调中完成所有其余的工作。由于在这一点上,它已经在一个线程中,因此不一定需要为下一个请求产生另一个线程。只需在第一个请求之后执行所需的剩余部分,就可以在已生成的现有线程中执行。


在一个侧面说明,这是现代编程世界一个非常普遍的问题...

I Hate Programming

+1

尼斯的答案,以异常的“盲目” .. –

+0

@约翰我可以看到混乱 - 我只是“盲目”的意思,因为没有立即观察结果。相反,一个新的线程作为另一个“人”来“告知”你“线程”中的“变化”......换言之,另一个线程负责工作,调用线程不必关心关于确切的细节。他们只是想要结果。想想产生一个新线索,比如说雇用一个能为你工作的新员工。你给他们一个任务,你让他们去完成这个任务,而你继续其他事情。 –

+1

我知道什么是盲目的,这是一个很糟糕的用途。我仍然+1你的答案。 –