2017-09-05 148 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你的答案。 –