Component Development: Using asynchronous, non-blocking web requests (I/II)
Just as a general note up front: This blog post will be split into two parts. The first part will focus on using asynchronous requests with the Embarcadero REST library and the second part will cover using asynchronous HTTP requests using the HTTP request class provided with Delphi.
Firstly, we need some motivation to make any changes at all. My last blog post showed that the Pexels component was already working fine on multiple platforms. However, working and compiling does not mean that it is ready for deployment into real life – aka business environments.
Whenever requesting something from an external source, you have to consider the delay it might cause. In this particular case, delay is caused when requesting content from the web.
The Pexels component has two key areas where conent is requested from the Web:
- interaction with the Pexels REST API to get a list of all the available pictures for a certain keyword
- requesting an image using an URL that was retrieved from the API
I compiled the component for Windows and MacOS. Both times it worked fine. Even with delay caused by my slow internet connection. I never tried with iOS though as I know it would not work.
As a developer we always have to remember that even though the code executes fine on a certain operating system, we also have to be aware of the rules imposed by that system. Mobile platforms have very strict regulations when it comes to blocking the device. Apple will terminate your app if it gets into a blocking stage. Very nasty for the user as your app might work within a company network, but it will fail using 3G on a train, e.g.
How can we implement in a non-blokcking way? Asynchronous implementation to the rescue! Fancy name, must be complicated to implement.
“In the old days” it was rather tricky, but as I mentioned many times in previous blog posts, Delphi’s language features have grown with the times since its initial release in 1994.
This blog post will look at the work that needs to be done in order to realize the first web request in my example component using the Embarcadero REST library. As you will see, the library nicely abstracts from the asynchronous nastyness — some of the general rules do still apply of course.
Let’s get back at the terminology first. Asynchronous means that two lines of source code that you entered after another might be executed at the same time, thus, you cannot expect the code from the line before already has been executed completely. More to the point, you have to deal with threads. Delphi offers a class called Thread – and also a mighty new class framework that is not to be the concern of this blog post. As already mentioned, the REST library will not make you do the hard work.
There is only one nasty tid bit to remember whenever having mutliple things execute at the same time: user interaction. User interaction with a graphical user interface to be more precise. The graphical user interface runs in the so-called main thread and is not supposed to be touched from any other thread as it will lead to synchronization issues. I really do not want to dig too deep into this field. Let’s just say that you never meddle in any GUI stuff whenever you are writing code that is run at the same time as the ‘main thread’.
Giving an example: You have a Delphi VCL forms application with a main form. The form contains a TButton that the user can click on. Let’s assume the button creates a separate thread to request information from the web and that information is supposed to be shown in a TMemo. You cannot have the thread meddle in the TMemo — you have to read the information when the thread has been executed completely, meaning that the thread needs to put the data into a common repository that is safe to use. Using the REST library these principles are already being honored, there are still some pitfalls that I will point out while explaining the modifications to be done.
Let us dive right into the source code that calls the Pexels API in order to retrieve data:
// execute the request Request.Execute; // (1) // read content from web lJson := Response.Content; // (2) // deserialize into object lItem := TJson.JsonToObject<TRequestItem>( lJson );
The code above is not surprising. We execute the web request and then read the result using the Content of the Response object. Having a JSON string, we can deserialze it into our object.
This means – to explain it very simplistic – every line is executed when the one before it has completed completely. This also means that the time between (1) and (2) might be several seconds! May be so many seconds that the operating system kills our app.
Thus, we need to make (1) a call that is non-blocking. But how can we make sure that (2) is executed after all the content has been retrieved? How do we execute the web request in a non-blocking way?
Looking in the documentation we find that there is not only Execute , but also ExecuteAsync available. Thus, the second question is solved.
The fingerprint of the asynchronous method is nothing to be scared off:
function TCustomRESTRequest.ExecuteAsync( ACompletionHandler: TCompletionHandler = nil; ASynchronized: boolean = true; AFreeThread: boolean = true): TRESTExecutionThread;
The first parameter allows us to specify a method that is called when the execution has completed. Exactly what we need! The next two parameters are more technical, so we simply keep the default values at first.
One thing to consider is when converting non-asynchronous requests is the fact that local variables must become fields in most of the cases so that we can use them in the initiating method as well as in the method that is called when the request was executed. Furthermore, if multiple calls to a REST API are needed, you need to wrap the execution call to the API into its own method as well, to make sure that you can call it multiple times (with different parameters, of course).
procedure TdmPexels.RequestPage(APage: Integer); begin Request.Params.ParameterByName('page').Value := FCurrentPage.ToString; Request.ExecuteAsync( EvaluateResponse ); end; procedure TDmPexels.RequestPictures(ADataset: TDataset; AKeyword, AAPIKey: String; AMaxPage : Integer); begin FCurrentPage := 1; // set parameters for request Request.Params.ParameterByName('Authorization').Value := AAPIKey; Request.Params.ParameterByName('query').Value := AKeyword; if Assigned( ADataset ) then begin ADataset.Open; // move parameters into fields FDataset := ADataset; FMaxPage := AMaxPage; RequestPage( FCurrentPage ); end; end;
To clarify my point I present a rather detailed chunk of my source code. You can see that the method RequestPictures sets the page to be requested to 1. Whenever another call to the API is needed, the method is called with another page number. Also pay attention to the fact that I move the method parameters into fields. Of course, one can implement the parameters as properties right from the start, but my design called for this approach.
The method RequestPage does nothing but set one parameter of the REST request and then execute the non-blokcing request. It also specifies that the method called EvaluateRespone is to be called when the request completed.
Mind the fact that we do not need to deal with any possible error states here. This is either covered by the REST components that are part of the library or are handled on a higher level. Also note that we have absolutely GUI-feedback at this stage — or do we?! Well, we open a dataset which looks rather unguilty of doing anything with our GUI. However, any dataset can be bound to a datasource in Delphi. Thus, if the dataset that is handed to the thread was connected to a datasource we would definitely have a problem! These are the things you still need to consider. Opening the dataset causes a grid to redraw itself …. big problem. We have a thread that influences the graphical user interface.
This is exactly what the second parameter of ExecuteAsync is for. The default value has been chosen perfectly. It tells the thread be created in the main thread (‘synchronized’) and thus the thread is able to touch the GUI without hurting us – although this should still nott be the case in good application design. You should point out to the users of your components that they need to disconnect the datasource before running any requests, for example. In very technical speak we can also say that our method RequestPictures is not thread-safe because of the usage of the dataset if there are connected datasources.
Getting back to the implementation, it will work as we used the default value, but we should make sure that any datasources are disconnected.
Let’s look at the last part:
procedure TdmPexels.EvaluateResponse; var lJson : String; lItem : TRequestItem; begin lJson := Response.Content; lItem := TJson.JsonToObject<TRequestItem>( lJson ); try // // lots of secret stuff... ;) // if (Length( lItem.Next_Page ) > 0) or (FCurrentPage < FMaxPage) then begin FCurrentPage := FCurrentPage + 1; RequestPage(FCurrentPage); end; finally lItem.Free; end; end;
I removed the part that reads the data from the REST API. We focus on the fact that this method is called after the request has completed. Thus, we are now allowed to access the Content . This code looks exactly the same as before. We also determine if another page is available and if yes, will call RequestPage again. This is the reason why FCurrentPage needs to be a field as well as FMaxPage .
Believe it or not that’s it.
We completely switched from a blocking request to a non-blocking behavior as the result of the request is evaluated when it has available. Our app – especially the app accepting user interactions – is never blocked.
So, how difficult was it? How many new scary classes did we have to look at? I will bever understand why Embarcadero has to put the following scary sentence in its documentation:
ACompletionHandler –Specifies an anonymous method to run after completing the request execution.
Anonymous method? We are all proud that Delphi can handle these now, but we are busy enough dealing with getting to think in asyncronous terms. We do not need to think about another rather new concept at the same time! Use a plain method and start coding!
So, one final thing to do, to be on the safe side. As this is a major hurdle at first.
Our demo app calls the Pexels component after a button click and updates its grid. As exaplained above this means that the dataset is linked to a datasource. This leads to a very easy solution to disable the datasource during the execution of the request:
// !!!!! // please read explanation as this might seem // like the solution, but it will fail // !!!!! procedure TForm1.btnFetchClick(Sender: TObject); begin // disable datasource dsResponse.Enabled := False; Pexels.Keyword := txtKeyword.Text; Pexels.Fetch; // (1) // enabled datasource dsResponse.Enabled := True; // (2) end;
Will it work? NO!
The line marked with (2) is executed right after (1). That means that the datasource is enabled right after it was disabled. There is abolutely no delay. No web request will be that fast and it will — thankfully — fail in any case. Mean bugs of this sort work ‘sometimes’ and show up at the customer site…
Thus, please forget to do it like that. We need a different implementation to disconnect and connect the datasource. E.g. we could extend the Pexels component to offer two more events:
In both of these methods we handle the datasource.
I understand that this is a rather complex topic, but I hope I eased up the start a bit for many of you!
The next blog post will show how to change the blocking web request to retrieve an image to an asynchronous implementation.