|What Is Asynchronous Programming?|
|Written by Mike James|
|Friday, 28 June 2013|
Page 2 of 2
The idea of having an event handler return almost at once is the ideal in a single threaded event driven system.
This is often refered to as a non-blocking call as opposed to a blocking call which stops the rest of the system moving on because it takes so long. In most cases a blocking call can be converted to a non-blocking call by the simple expedient of returning before its job is done.
We have already met one big example of converting a blocking to a non-blocking call when the idea of using a new thread to get an event handlers task completed.
The idea is that if you have a event handler
then you convert it into:
This looks very simple but it raises a whole new problem. Usually the way that the applciation behaves depends on the completion of the event handler's task.
For example if the event handler was say loading an image over the internet then perhaps the next action that the user takes depends on its completion. The point is that now the application needs to know when the getWorkDone function has completed.
In other words using a separate thread to complete the work of the event handler has produced a synchronization problem that disrupts the logical flow of the program.
It also introduces other problems associated with the thread shareing resources and accessing the UI but these are not really central to the problems of asynchronous code.
Introducing a thread makes things more complex but we have little choice.
The idea of converting a blocking function into a non-blocking one extends to functions which are not themselves event handlers. For example consider the getWorkDone function which takes a long time to complete.
You could make it non-blocking by introducing a new thread.
In the case of many system or framework functions you don't even have to imagine that they create a new thread you can regard them as simply returning at once even thought their job hasn't been completed.
Using the non-blocking form of the getWorkDone function the event handler can be written more simply as:
Notice that now the writer of the event handler doesn't have to worry about creating a new thread or how to run getWorkDone to minimize the slowdown of the UI.
However this solution, neat though it is, introduces another problem. Usually the event handler would want to do something with the result of getWorkDone. In general when ever a blocking function is converted into a non-blocking function we have the problem of what to do next. Indeed we have a problem of exactly when "next" is.
The problem is when getWorkDone completes some seconds after onEvent has returned there is no calling function for it to return to. The calling function terminated a while ago by returning so that the UI thread can be ready to process another event.
This idea that a function might not know what to do after it finishes its task can be solved in many ways but they are nearly all variations on the idea of continuation passing - i.e. passing an explicit something to do when the function has finished.
The simplest and best know of these approaches is to pass a call back function to the non-blocking function that contains the code that is to be executed when the task is complete.
The onCompletion function is called when getWorkDone has finished. Notice that onCompletion looks a lot like an event handler and in fact there is very little difference. A call back function is just an event handler that responds to the completion of the non-blocking function. You could adopt a different syntax that allowed the assignment of a callback as if it was an event. For example:
Event handlers tend to be called repeatedly when the event is raised but callbacks tend to be related to one off events like a file finishing downloading.
Of course things are a little more complicated in that this sort of mechanism seems to modify the natural logic of a function. A function that was:
is distorted into:
and while this example may not look so bad the reality is usually much worse.
You might think of a function as loading an image, processing the image and then displaying it but you have to convert into a function that starts the download and an event handler that reacts to the completion of the download.
In practice a function could be decomposed into many event handlers or call backs each dealing with the completion of some non-blocking function or other.
Dealing With Async
Given that asynchronous coding is so prevalent what can we do to make it better.
First we need to try to explain what makes it worse.
Writing a collection of event handlers generally isn't a difficult thing to do and with the condition that they all return fast there isn't really any difficulty.
Event handlers tend to be self contained and one event handler isn't in any way a continuation of another.
Where things get difficult is when you make use of a lot of non-blocking functions to make sure that event handlers return fast. The problem here is that calling non-blocking functions means passing call backs or completed event handlers depending on how you want to look at it.
This is fine when you are only calling one non-blocking function to get a task done but this is usually not the case.
Typically you need to perform a number of tasks and if the functions are blocking you might write:
To convert this to a set of non-blocking functions would result in something like:
where it is assumed that each function is passed a callback as its only parameter. That is each function is passed a function which calls the next function in the sequence. Function A calls function B when it is finished and Function B calls function C when it is finished.
This pyramid of doom style callbacks gets much worse in practice.
For the final example consider a loop:
and now convert it into a non-blocking call:
Assuming that funcA takes a callback as its second argument. Yes that's correct an enumeration loop turns into recursion with non-blocking calls.
However even this isn't the most general case. In this case the function called by the loop don't depend on each others results - if they do then you have
There are lots of other problems - handling error conditions, dealing with exceptions, conditional execution and so on.
All in all asynchronous programming using callbacks in non-blocking functions isn't easy.
The problem is that writing an algorithm in asynchronous non-blocking style completely destroys the natural logic of the task as expressed in a synchronous blocking form.
Are there any solutions?
Yes - see Promises, async and await, yield and any number of flow of control libraries that convert the convoluted callback structure into something more familiar.
We will take a look at these solutions in the second article.
To be informed about new articles on I Programmer, install the I Programmer Toolbar, subscribe to the RSS feed, follow us on, Twitter, Facebook, Google+ or Linkedin, or sign up for our weekly newsletter.
or email your comment to: firstname.lastname@example.org
|Last Updated ( Saturday, 19 August 2017 )|