To understand asynchronicity better lets talk about some basics on how computers actually order execution of consecutive blocks of code. The main unit responsible for processing code that we developers write is, well… processor. While talking about simple actions with all variables known in our code, the time of program execution is dependant directly on the capabilities of the processor. But there are programs using data from other sources e.g. communicate over network or request data from the hard drive. Some of those actions could take a long time and processor would’ve sit idle while waiting - this is definitely time wasted.
There is also important concept to add - single and multi threaded environments (not to confuse with synchronous and asynchronous programming models). For example, synchronous (or asynchronous) model can be used in single or multi threaded system.
Synchronous actions means they are synchronised e.g. to one main clock. This means that the thread or process access to a critical section of code must be synchronised among the others, that is to make sure that only one of them can execute that part of code at any given time. Thread is separate running program (the smallest sequence of code instructions with isolated values for its own use within process) which can be interleaved with other programs by the operating system.
In this programming model only one thing can happen at the given moment (on each thread). Picture above presents single-threaded, synchronous approach - that one thread will work on the next task only if it finishes previous one. Basically, a program is a series of code instructions that are executed consecutively.
It is possible that a function execution takes very long time. This means that, the rest of code is going to sit and wait till its execution has completed - potentially blocking parts of the code's flow that, for instance, don’t depend on the successful completion of that function. This certainly constitutes a problem, but it’s not the only one. What if the function’s execution doesn’t ever finish? The program may get stuck. The good news is that it won’t if we use the correct approach to the problem.
In synchronous system, we could come up with solution of starting additional threads of control. If we start adding more threads (number of available threads depends on the machine specifications, CPU & memory), then these tasks can be picked up and worked on in parallel as on the picture below.
In this environment, each thread can pick separate task and process it until finished, then - when becomes available again - can execute next task. This also means we can have, for example, two functions that will perform some actions and then return their results, resynchronise and use them in future execution. This might be the always pick-up first solution but, as mentioned before, running additional threads will be somewhat dependant on machine specs, while also being harder for developers to maintain proper synchronisation and prevent crashes (deadlocks, live-locks, races over resources).
In asynchronous programming model things can also happen seemingly simultaneously. Async operations don’t wait for others to finish before proceeding - a thread doesn’t actually stop its work - it will switch to other available task. In single-threaded environments though, this won’t be a true parallelism as in multi-threaded ones. When the main code splits to perform some side action and would normally wait for result - it continues to run here. So actually instructions that are asynchronous run at the same time(while being separated) as the rest of code. This can happen on one thread, which can switch between tasks and grab the results it needs to execute further code instructions. There goes a nice analogy to explain the difference. Imagine being a cook (thread) and having an order (program) to prepare eggs and toast (tasks).
Synchronous way you:
- Cook the eggs, waiting for them.
- Cook the toasts, waiting for them.
- Serve meal.
Asynchronous single-threaded you:
- Set eggs to cook with timer.
- Set toasts to cook with timer.
- You can actually clean the kitchen (or do whatever else) while waiting.
- Take the eggs and toasts off the heat (just like collecting data)
- Serve the meal.
Following with more technical example, network or hard drive request for data can be made using asynchronous calls while the program still runs. Looking at the picture above, let's say task 1 (T1) requires some information from the network, it calls another function to do it, which can be represented by T2. The thread saves the state of T1 and executes T2, then returns its result to T1 and the program continues with obtained data. So actually, whenever function that needs to perform asynchronous action is called, it will then return some value or it can perform an additional action based on instruction we pass to it - those are callback functions. Additionally, if the code is executed in multi-threaded environment, then all of threads can work taking advantage of asynchronous programming model passing data between them.
Now when we know what async programming is, let's take a quick look at the benefits it can gives us. Crucial thing for any application is, of course, it’s usability - meaning how friendly is app for users, does users perceive it as smooth and enjoyable to use. A good example for this might be clicking button which causes to run code that will perform request for external resource. In this case synchronous programming would fail in usability as the UI would be blocked for further use until the data came back. Asynchronous approach lets user move freely in the UI while fetching data, which is the main reason of why nowadays so many apps and frameworks use this model.
The other important advantage is overall application performance boost. While the request is being performed by application, it is noted that around 70-80% of the working time would get wasted while waiting for the external tasks. So this time can be saved by asynchronous approach - when the task(s) delegates further execution to other process it then saves its state and can work on the next instructions. The continuation of returned task can be then taken by any of the available threads (in multi-threaded environments).
Additional thing that should be noted as benefit is that programs that works not in the straight A to B line can be expressed in a more convenient and readable manner. However, there are many cases that doesn’t require use of asynchronous approach and can even make things more complicated.
When to use/not use async programming
Asynchronous programming is a great thing, but like everything in tech field, has both pros and cons. In recent time this model has became used more frequently by many programmers. While it helps solving many use cases it can also cause some unexpected problems, thus it shouldn’t be used in every possible scenario as a grab & go solution - specially if you’re not certain that you need it.
That being said, I will try to highlight few do and don’t points below:
- Asynchronously updating records that are dependent, or depended upon, is generally a bad idea, as it will sooner or later cause your data to be not synced and messy. It’s way better to use it for processing independent data.
- Really helpful for pre-loading data, while showing user other content of the application, but be careful on mobile not to overload app with async operations as those are more resource consuming.
- If you are using single database without connection pooling, then it won’t be any point to use async calls, as the server will still be the bottleneck.
- You shouldn’t go for async if app will use simple, short running operations - specially performed only by CPU.
- In addition to the one above - if your app can stop while fetching data (rather not applicable for UI apps) - you should consider not using asynchronous approach.
- Use async when tasks number is large, specially if they might block each other.