Asynchronous programming is a set of techniques that allows applications to perform multiple tasks simultaneously, without blocking the main execution flow.
If your computer could only do one thing at a time, it wouldn’t be very useful. Imagine that every time you open a file or visit a webpage, your computer would “freeze” until it finished. That wouldn’t be very practical!
In fact, your computer performs thousands of tasks in a “simultaneous” manner. It manages WiFi, renders the screen, reads the hard drive, runs a program, and reads the keyboard. It does all that “more or less” at the same time.
In the same way, you need to prepare your programs to do several things “at once”. This is what we call asynchronous programming, creating programs that can operate in a non-fully sequential manner.
Advantages of Asynchronicity
Why is it important to add asynchronicity to your programs? Well, there are several reasons and advantages associated with being able to execute tasks in a non-sequential manner.
The main reason to apply asynchronicity is that it improves the user experience in terms of responsiveness. That is, giving feedback to the user through the user interface.
Without asynchronicity, for example, your program couldn’t even display a “loading” window with a spinning circle while loading data from somewhere. It would be blocked, and you couldn’t do anything at all.
Another major reason is that, under certain circumstances, it can lead to improved resource efficiency (resources that are limited and can create a bottleneck).
For example, imagine that the processor is idle, waiting for a response from a website. With asynchronicity, it’s possible to take advantage of that wait time to “insert” other calculations in between.
Difficulties of Asynchronous Programming
Of course, not everything is going to be advantages. Asynchronous programming is objectively more difficult than sequential or synchronous programming. It always has been and always will be.
This is simply because it is inherently more complex. It will always be more complicated to manage multiple things at once than just one (just like juggling five balls is harder than juggling one).
One of the problems you will have to consider is temporal synchronization. Now one thing doesn’t follow another; instead, they occur in parallel or semi-parallel.
So your mind needs to stop thinking of your program as a series of tasks that execute sequentially and start thinking of a schedule with multiple execution lines that can be parallel and interfere in a non-deterministic way.
Another major problem is access to shared resources. If a resource is actively being used by one process, it is very likely that another process cannot use it. If both try to access it, you will likely encounter an error.
Finally, you will also have difficulties sharing memory. It’s even possible that one process is “doing its thing” while another process modifies a variable. Or that one task cannot access the memory of another.
All these problems were not present in synchronous programming. To manage these issues, we’ve created a lot of tools and resources, such as mutexes, semaphores, monitors… but still, you will always have added complexity in the design.
Mechanisms for Asynchronous Programming
In addition to the tools I’ve already mentioned, different programming languages have incorporated various mechanisms to make implementing asynchronicity easier.
Some of them are:
- Callbacks / Events: Functions that are passed as arguments to other functions and are executed after an operation is completed.
- Promises / Futures: Objects that represent a value that may be available now, in the future, or never.
- Async / Await: Syntax that allows writing asynchronous code in a way that is more similar to synchronous code.
- Threads: Threads allow executing multiple tasks in parallel within the same process.
- Co-routines: These are functions that can pause their execution and yield control to other co-routines.
- Event Loop: A mechanism that allows handling input/output operations in a non-blocking manner.
These mechanisms provide various ways to manage the execution of concurrent and asynchronous tasks, each with its own advantages and disadvantages.
Of course, the choice of the right mechanism depends on the programming language and the type of application you are developing. (We will explore these in depth in the different articles of this chapter).