Page 1 of 2
Asynchronous programming has become very important in the last few years, but many programmers find out about it by doing it. So what exactly is asynchronous programming, why is it necessary and why is it growing in importance?
This is the first of two articles on asynchronous programming:
- What Is Asynchronous Programming?
- Managing Asynchronous Code - Callbacks, Promises & Async/Await
Before getting started, this is a down to earth introduction to asynchronous programming and not a mathematical analysis. It is possible to make the whole subject seem so complicated that it is a miracle that we make use of it at all.
Asynchronous programming has been with us from the very early days of computing because of the need to make the best use of the hardware. But recently it has become almost the standard programming paradigm. So much so that you could say that most programs written today are object oriented asynchronous programs.
Often the programmer is fully aware that what they are doing is object oriented but only vaguely aware that they are writing asynchronous code.
It all starts with the User Interface (UI). If you allow the user to interact with your program by clicking buttons and selecting object then you have an immediate problem
What is your program doing when the user isn't clicking or selecting something?
The obvious answer is nothing at all. Your program has to wait until the user gives it something to do. This is the case whenever your program interacts with the user even if it is simply waiting at the command line for the user to type something in.
You can implement this sort of system using polling. That is your program can go around the UI asking the question "has this button been clicked" and "has this button been clicked" and so on. This ties up resources looping around the UI just checking in case something has happened.
This isn't how most UI frameworks organize things. Instead they implement an event handling system. The user clicking a button is defined as an event and you can associate code with each event - the event handler. When the event occurs the event handler is run.
This is such a familiar pattern that we hardy give it a second thought but you should. For example, suppose there are three buttons on the screen and the user clicks on all three at high speed - do three different event handlers get started?
In most cases the answer is no only one even handler is started at a time. The most common event handling architecture is single threaded - that is it only has one thread of execution and at any one time only one instruction in your entire program is being obeyed.
This single threaded event handling system is nearly always implemented using an event or message queue.
The idea is that while your program is doing nothing its thread looks after the event queue. When an event happens a record of the event is added to the queue and when your program's thread isn't doing anything it looks at the event queue and takes the first event from the front and starts running the corresponding event handler. When that event handler completes the thread goes back to the event queue to process any events that might have happened in while it was busy.
So events are added to the event queue and the UI framework provides a dispatcher that runs on your thread and calls the event handlers as needed. At any moment the program's thread is either in the dispatcher finding out what event it has to process next or in an event handler. Of course if there is no event to process the thread just idles waiting for an event to be added to the queue.
This is a single threaded event system.
The OS, Threading And True Parallelism
You might at this point ask the very reasonable question of how an event gets put into the event queue while the program's thread is off running an event handler? The answer is that the operating system has lots of threads and it can respond to the user and place a message into the event queue.
Now we hit a subtle point that might confuse you if you don't know much about operating systems and hardware.
Until quite recently there only ever was one thread of execution in a typical machine. The multiple threads that were provided by the operating system are created by preemptive scheduling - only one thread runs at any one time and the OS switches which is the active thread.
In this sense the entire machine OS is a single threaded system. The main distinction being that the OS is preemptive and threads can be interrupted part way though and an event driven system is cooperative in the sense that an event handler, once started runs to completion.
It is this preemptive - cooperative difference that is important.
The final complication is that modern machines have multiple cores and this means that they can support more than one thread of execution and hence are capable of true parallelism.
For the moment we can ignore this detail - important though it is.
The most important distinction is that in a preemptive system the programmer doesn't have to worry about keeping the main thread occupied. Every few milliseconds the hardware interrupts the running thread and starts it running the operating system. The operating system picks another process to start and runs it. In a preemptive system the one thread is automatically shared fairly between all of the processes no matter how they are programmed.
So a single threaded event system works by placing events in a queue and processing them one-by-one calling the appropriate event handler. The event handler runs until it completes when it returns control to the dispatcher which deals with the next event in the queue.
This is an asynchronous system because you cannot say exactly when anything is going to happen. There is no set order that your code will be executed in. You may have a program of 1000 instructions say but you cannot say what the order of its execution is. What happens depends on what buttons and options the user clicks. Event handlers are called in many different orders.
If you just consider this idea for a moment it might seem amazing that we can write asynchronous code at all. All those different ways it can be run! Of course the point is that the system is made simpler by the restriction that only one event handler runs at a time and it always runs to completion. The interactions between event handlers are made simpler by this restriction. To put it another way event handlers are atomic, i.e. indivisible, actions.
Now we come back to the problem of the single threading.
If any event handler takes a long time or worse never returns then the UI seems to freeze.
The reason is simply that if the event handler never returns the thread to the dispatcher and hence no other event handler ever gets to run. If the event handler holds the thread for any noticeable length of time then events are not processed and the user thinks that the UI is unresponsive for that time.
The cure for this problem is to keep the work done by any event handler to a minimum. In a sense the event handling system is a cooperative multitasker and it in this case it is the duty of every event handler to return control to the dispatcher as soon as possible. The fair sharing of the thread of execution is a matter of cooperation between all of the code that needs to run i.e. the event handlers.
So what do you do if an event handler has a substantial amount of work to do?
The textbook answer is that you simply use the event handler to set up another thread and get the new thread to do all the work. The event handler returns almost at once and the new thread continues to do the job.
This use of a new thread causes problems of synchronization that we will return to a little later. In short adding threads to a single threaded event system makes it much more complex.
Hence the non-textbook non-approved way of dealing with long running event handlers - doEvents.
Some languages, Visual Basic for example have a special doEvents command which acts like a yield command - in that it transfers control to the calling routine, the dispatcher in this case, which then processes all of the pending events and when done transfers control back to the suspended event handler at the instruction following the doEvents. The state of the event handler is unchanged at it continues on as if nothing had happened. In other words the doEvents command does what it says it does - it pauses the event handler and allows the dispatcher to "do" any pending events so keeping the UI responsive.
Languages that don't have a doEvents or similar can often implement the same thing if they provide access to the dispatcher from running event handlers. Basically the event handler puts an event record at the end of the queue that restarts it and then returns control to the dispatcher.
This seems the ideal solution.
A long running event handler cooperatively yields the thread of execution so that other events can be processed and the UI kept active. When the events have been processed then the long running event handler is resumed.
It is indeed a good solution but it has a flaw that is so serious that many give the advice that it is to be avoided at all cost and its bad practice.
The flaw is simply that the event handler that yields control is now no longer atomic.
It is interrupted at some point in its code and other event handlers could change things that it is using. Worse it could even be called again as the result of an event in the queue. Most event handlers are not reenterable and things could get very complicated.
However a programmer, aware of these difficulties can make doEvents work. You have to take account of the fact that shared resources could change after a doEvents and you have to reject any attempt to start the event handler again while is is still active.
Both of these are usually easy to achieve and arguably much easier than starting a worker thread to get the same job done.
However for beginners the rule "don't use doEvents" is a safe one.