Concurrency in Dart — Part I
An introduction to concurrency in dart and in general.
If you are coming from a world of the language of parallel processes or threads like Java/C++/Kotlin etc then at the start it might be difficult to wrap your head around JS/Dart single-threaded approach. You might have heard that Javascript and Dart are single-threaded, let's deep dive into this approach. I will help you to create a good understanding of underlying concepts here.
Parallelism and Concurrency
When multiple tasks are executed on multiple cores on a machine it is called parallelism. It can be done via threads or processes on an operating system.
Concurrency on the other hand talks about where tasks take their turns to get executed on a single core. It's like a single person taking orders from a people queue.
Important points:
- No race conditions can happen between asynchronous tasks in a single-threaded environment like Dart as there is only one task running at any point in time.
- You will still need a way to enforce serialization at places where you want to enter an async function only once and others have to wait for the previous invocation to complete. We will discuss this with an example later.
- Multiple threads can have simultaneous access to memory in case of parallelism and for managing race conditions we have to use systems like mutexes which enforce atomicity of instructions.
- Dart has a way to create threads via Isolates but they don’t share a memory with other Isolates and communication can happen only via messaging.
Synchronous vs Asynchronous code
Synchronous means execution of code in step by step manner. In asynchronous execution is out of order, here certain tasks are rescheduled to be run in future. If you see in the below example, in the case of the asynchronous actual order of output is different from the written order.
Output:
First
Second
third
Output
Third
Second
First
The event loop
In dart single thread runs in what is called isolate. The isolate used by the dart itself is called the main isolate. It has its allocated memory area and no other dart isolate can access this memory. Dart uses an event loop to execute tasks that had previously been postponed. The dart has two queues: a microtask queue and an event queue. The microtask queue is mostly used internally by Dart. The event queue is for events like user touching screen, keystroke, data coming from database, file or remote server etc.

- Synchronous tasks in the main isolate always run immediately. You cannot interrupt them.
- If any long-running task needs to be postponed then Dart puts them in the event queue.
- Once synchronous tasks are finished then the event loops check tasks in the microtask queue and puts them on the main isolate for execution. This is continuous until the microtask queue is empty.
- If the microtask queue and synchronous tasks queue are empty then the event loop starts picking up tasks from the event queue.
- If any new tasks enter the microtask queue then event loops pick them up before picking up any further tasks from the event queue.
- This process continues until all of the queues are empty.
An important point before we conclude this Part I
Although the dart is single-threaded it does not mean you cannot have tasks running on another thread.
- One example is when we request to read a file then that work actually happens on its own process in the system and once the work is finished it passes the result back to dart via event queue. A lot of dart I/O work from dart: io library happens this way.
- Another way to perform work on other threads is to create a new Dart isolate. Different isolates can talk to each other via messaging. Internally isolates are implemented via operating system methods such as thread and processes but internal implementation details are hidden from the application programmer.
Further readings: