I am relatively new to the RestRequest tools. I am hoping somebody has solved the issue of waiting for the async call to finish...
I have a program which makes hundreds of different api calls, the call then processes the result, and passes the result back to the procedure which called the api's execution, which can then continue...
Only problem as always with doing such calls without threading, is that the software hangs until the call is complete... To try and remedy this I changed RESTRequest.Execute;
to RESTRequest.ExecuteAsync();
, but now my problem is that my code continues without waiting for the restrequest's response.
again to try and bypass this problem I have tried several solutions, even api.RESTRequest.ExecuteAsync().WaitFor;
(which throws an error thread error: the handler is invalid (6)
)
Is there any way at all that I can change the below function to run as a separate thread (only the execute part is important to run in thread)... Basically I just want to show an animated loading icon everytime the function is called, and for the rest of my code to wat until this function completes...
I hope there is a simpler solution than to start using full on multithreading..
Code
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;
ExecuteAsync()
runs in a worker thread (the TRESTExecutionThread
object that it returns). However, ExecuteAsync()
has an AFreeThread
parameter that defaults to True
. As the documentation clearly states:
When the
AFreeThread
parameter is set to False, this method returns a reference to this execution thread.Note: If the
AFreeThread
parameter is set to True, the method returns an invalid reference.
So calling WaitFor()
on the returned object pointer will crash by default.
Even if the returned object pointer were valid when AFreeThread=True
, calling WaitFor()
would still crash. When a TThread
object's FreeOnTerminate
property is set to True
, the object frees its underlying API handle when the thread terminates, which causes WaitFor()
to fail with the "handle is invalid" error. TThread.WaitFor()
has a logic bug 1 that does not handle the case when TThread.FreeOnTerminate=True
.
(1 That bug was introduced all the way back in Delphi 6, when TThread
was rewritten for Kylix support, and it was never corrected in later versions.)
If you want the caller to wait for a response from the REST server, either:
use Execute()
instead of ExecuteAsync()
, or at least set AFreeThread=False
so you can call WaitFor()
(or equivalent, like MsgWaitForMultipleObjects()
) on the thread object, and then deal with the consequences of performing a wait in the main UI thread:
use Execute()
, but move your entire logic to your own worker thread, and have it sync with the main UI thread as needed.
The better solution is to simply not wait at all, which means redesigning your code flow. Continue using ExecuteAsync()
, but pass it a completion callback that will be called when the request is complete, and let that callback drive the next step in your code. Don't actively wait for ExecuteAsync
to complete, let it notify you.
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;
Extending onto Remy's answer...
"but now my problem is that my code continues without waiting for the restrequest's response"
This is the exact purpose of Async requests. You're not supposed to wait, you're supposed to send the request and move on to something else. You're also supposed to provide a callback procedure if you wish to capture the result, but I don't see you doing that in your code.
Synchronous and Asynchronous calls are quite different design architectures which cannot be simply switched between as trivially as you may wish. You can't just change Execute to ExecuteAsync without also re-designing the rest. Execute
by nature waits for the response within the calling thread. However, ExecuteAsync
by nature spawns the request in a new thread, so that your code can continue while it's doing your work in the background. There is no waiting involved in Async
requests - it defies the whole purpose of them.
The most ideal option in your case is to chain one request off of the other's response. Meaning, send your first request only. Once you receive the response, then send your next request from within its response callback.
"...since I want to create the function as a sort of "global api call" function to make coding a bit less..."
That's perfectly fine. You can still have one single main procedure which performs all the work. It's when to make each of these calls which matters here. A button's OnClick event is never the appropriate place to do such a thing, though. The only thing that button should do (since it's in the main UI thread) is initiate a request. If you want your UI to be responsive at all after this point, then that request should spawn a new thread in some way, shape, or form.
In any case though, WaitFor
just won't cut it for your request. You want a responsive UI. WaitFor
will defy this requirement, and lock up your application's main thread anyway. The whole purpose of doing this work in another thread is to show a "waiting" animation, which will not be animated if your main thread is busy waiting for anything.
EDIT
I actually thought of another possibility. Still spawn your first request only. But instead of "chaining" one request off of the other's response, just do all the rest of the work from within that first request's callback. Since at this point, it's already in a thread, there's not necessarily a need to spawn yet another thread for the next request. Just perform the remainder of what you need after the first request, in the existing thread you've already spawned.
On a side note, this is a very common problem in the modern programming world...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With