[Dart]: Understanding Isolates

[Dart]: Understanding Isolates

As you may know, Dart is a Single Thread Language where operations are executed one at a time, one after the other, in a purely synchronous manner.

In other languages like C++, multiple threads can share the same memory and run whatever code you may want. However, in Dart, each thread has its own isolate within its own memory, and processes events independently.

How Dart Manages Execution Flow

To understand how Dart manage its sequence of operation, we will need to look within a Flutter application.

When a Flutter application starts, a new thread process is created and launched. This thread is the only thread you'll work with throughout your application.

There are 2 different types of threads that handle tasks:

  • MicroTask
  • Event Queue

MicroTask always runs first, and developers do not need to worry about its internal tasks since it is taken care for us.

When you call a future, the task is placed on an event queue once the Future completes.

After the main method runs, the Event Loop is executed.

  • This invisible process determines how and in what order your code executes, depending on the content of the MicroTask and Event Queue.

Key differences - MicroTask vs Event Queue

A MicroTask handle short internal actions such as disposing of a resource. After this is done, it will hand back to the Event Queue.

An EventQueue on the other hand, is used to reference operations that result from external events such as IO, gesture, drawing, streams, timers, or futures.

Future and Async Methods

Before diving into Isolates, Let us understand how Futures and Async methods works

  • A Future is a task that runs asynchronously and completes later.
  • After it is created, it is added to an internal array which is placed in the Event Queue.
  • While running, the Future's status remains incomplete, allowing the next lines of code to execute without waiting.
  • Async methods are built around Futures.
  • It runs synchronously, up to the first "await" keyword then pauses execution of the remaining codes of that method until the Future completes.

MultiThreading

Question: Can we run parallel codes in Flutter?

Answer: Yes, this is made possible by using "Isolates"

What is an Isolate?

As explained earlier, Dart code runs on a single thread called isolate. However, the major difference is that, isolates do not hang together. They only communicate to each other through messages sent over "ports", without sharing any memory

Each isolate has an event loop, which schedules asynchronous tasks to run.

Ways to create an Isolate

  1. Through low-level solutions.

As explained earlier, Isolates do not share memory and communicate using messages sent over ports. Therefore, in order to make this work, we need to establish communication between the "Caller", and the new Isolate

  • A SendPort is used by an isolate to convey messages.
  • A ReceivePort is used by another Isolate to listen to those messages.
  • Both the Isolate and the caller, will reference this port for communication.

A simple scenario:

Create a Dart project that returns 'Hello World' using Isolates.

a. To create a Dart Project, use the following command

dart create project_name

After having created the project, you will notice 2 main folders, bin and lib.

  • Bin is where your main dart file lives
  • Lib is where you will be keeping your helper functions

b. Within the main method,

  • Create a temporary ReceivePort

final receivePort = ReceivePort();

  • then instantiate a new Isolate.
  • This Isolate, will be taking a function, and 2 parameters, a value, and sendPort for communication.
await Isolate.spawn(callBackFunction, (
    value: 'Hello World',
    sendPort: receivePort.sendPort,
  ));
  • Listen for incoming messages.
receivePort.listen((value) {
    print('Result: $value');
  });
  • Define the callback function
callBackFunction(({String value, SendPort sendPort}) data) {
  data.sendPort.send(data.value);
}

Full Code

void main() async {
  final receivePort = ReceivePort();
  await Isolate.spawn(callBackFunction, (
    value: 'Hello World',
    sendPort: receivePort.sendPort,
  ));
  receivePort.listen((value) {
    print('Result: $value');
  });
}

// isolate
callBackFunction(({String value, SendPort sendPort}) data) {
  data.sendPort.send(data.value);
}

It is best practice to dispose of Isolate when it is no longer needed.

void dispose(){
  isolate?.kill(priority: Isolate.immediate);
  isolate = null;
}

That's one way to writing this code.

  1. Instead of manually managing ports and isolates, Flutter provides a convenient helper method called compute, which automatically handles message passing for you.
    It’s ideal for offloading heavy computations to another isolate without manual setup.

And thats' it.


References

Isolates
Information on writing isolates in Dart.

About Me

I am Zaahra, a Google Women Techmakers Ambassador who enjoy mentoring people and writing about technical contents that might help people in their developer journey. I also enjoy building stuffs to solve real life problems.

To reach me:

LinkedIn: https://www.linkedin.com/in/faatimah-iz-zaahra-m-0670881a1/

X (previously Twitter): _fz3hra

GitHub: https://github.com/fz3hra

Cheers,

Umme Faatimah-Iz-Zaahra Mujore | Google Women TechMakers Ambassador | Software Engineer