Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple REST Requests in parallel with FireMonkey

I'm building an Firemonkey app in Rad Studio XE7 where on single button click, I will need to do multiple( around 7) web service calls using TRestRequest. Every web service will return json object which will then populate dataset. I'm looking for a way to these calls concurrently and not have UI of the app to lock.

Which way do you recommend to do this? I saw that Embarcadero introduced new Task and Feature functionality for threading, but I'm still not sure if we can use that and how. Also, I saw that there is a function to execute TRestRequest asynchronously, using this function:

function TCustomRESTRequest.ExecuteAsync(ACompletionHandler: TCompletionHandler = nil; ASynchronized: boolean = true; AFreeThread: boolean = true): TRESTExecutionThread;

but I can't find any documentation on how to use this method and what it does.

Any input will be greatly appreciated.

like image 997
Emulic Avatar asked Dec 03 '14 18:12

Emulic


Video Answer


1 Answers

The ExecuteAsync(ACompletionHandler: TCompletionHandler = nil; ASynchronized: boolean = true; AFreeThread: boolean = true) method is indeed the way to go. As the name implies, it is asynchronous; meaning your program remains responsive after firing the request, or even after firing several requests.
However, it is important that you fire these different requests from different object instances; if you fire an ExecuteAsync from a TRESTRequest instance that is already performing an ExecuteAsync, the new request will get in the way of the existing request. You must create a separate TRESTRequest instance for each parallel call.

Note that its first parameter is a procedure; you pass a procedure of your choice as that parameter. The only requirement is that the procedure has the right signature; in this case, that it is a procedure that has no parameters.

The ExecuteAsync method is defined in REST.Client. (I've got Delphi XE-10.1. Berlin so it has one extra parameter, ACompletionHandlerWithError - which is called on error. The principle remains the same though).

Let's have a look:

function TCustomRESTRequest.ExecuteAsync(ACompletionHandler: TCompletionHandler = nil; ASynchronized: boolean = true;
  AFreeThread: boolean = true; ACompletionHandlerWithError: TCompletionHandlerWithError = nil): TRESTExecutionThread;
begin
  Result := TRESTExecutionThread.Create(Execute, self, ACompletionHandler, ASynchronized, AFreeThread, ACompletionHandlerWithError);
end;

What happens here is that a new thread is created in which the REST Request is executed. If the response comes in, it is handled by ACompletionHandler.
By default, ACompletionHandler runs on the new thread created by ExecuteAsync. If you want it to run on the main thread, you should set ASynchronized to true.

But how do you get access to that response, and make it accessible to the rest of your program?

FireMonkey's TRESTRequest class has a property Response, that refers to the TRESTResponse object that contains the server's response to our request.
Unfortunately, neither the TRESTRequest nor the TRESTResponse object are passed to our CompletionHandler!

So we need to somehow give the CompletionHandler this information. Fortunately, we can use a method as a CompletionHandler.

Let's assume that the class that is going to process the resulting data is called DataOwner. Our purpose is that DataOwner has access to the TRESTResponse instance associated with our TRESTRequest instance.
The easiest way to do this is to make TRESTRequest and/or TRESTResponse a member of DataOwner.

Assume your request was fired from a TRESTRequest instance named MyRESTRequest and processed by a function processResponse, you would use the following code:

type TDataOwner = class
   MyData: TSomeDataType;
   procedure GetData();
   procedure FillDataSet();
end;

implementation

procedure DataOwner.GetData();
begin
  // ... initialize MyRESTRequest here...
  MyRESTRequest.ExecuteAsync(FillDataSet);
end;

procedure DataOwner.FillDataSet();
begin
    MyData := processResponse(MyRESTRequest.Response);
end;

If you want to fire several requests in parallel, you need to use this pattern several times. It is unfortunate that we don't have direct access to the TRESTRequest instance or TRESTResponse instance in our Completion Handlers, because this means we have to do the bookkeeping ourselves. In other words, it is up to the programmer to make sure that the Completion Handler processes the right Response object.

like image 135
S.L. Barth Avatar answered Nov 15 '22 05:11

S.L. Barth