flutter
/

Dart Isolates – Parallelism and Concurrency

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Isolates – Parallelism and Concurrency

What are Isolates?

In Dart, all Dart code runs inside isolates. An isolate is a separate execution context with its own memory heap and its own event loop. Isolates do not share memory; they communicate by passing messages (like how processes communicate). This design avoids many concurrency bugs (like race conditions) and allows Dart to run on multiple cores efficiently.

The main isolate (where main() runs) is created automatically. You can spawn additional isolates to perform heavy computations in parallel without blocking the main event loop.

Why Use Isolates?

    • CPU‑intensive tasks – image processing, JSON parsing, complex calculations.
    • Keeping UI responsive – move heavy work off the main isolate (especially important in Flutter).
    • True parallelism – on multi‑core devices, isolates can run simultaneously.

Spawning an Isolate

To create a new isolate, use Isolate.spawn(entryPoint, message). The entry point must be a top‑level function or a static method that takes a single argument. The new isolate starts running that function.

DARTRead-only
1
import 'dart:isolate';

void heavyTask(String message) {
  print('Isolate received: $message');
  // Do some heavy work
  for (int i = 0; i < 5; i++) {
    print('Isolate working... $i');
  }
}

void main() async {
  print('Main start');
  await Isolate.spawn(heavyTask, 'Hello from main');
  print('Main continues (non‑blocking)');
  await Future.delayed(Duration(seconds: 2)); // wait to see isolate output
}

Note: Isolate.spawn is asynchronous and returns a future that completes with the isolate reference. The new isolate runs independently.

Communicating Between Isolates

Because isolates don't share memory, you need to pass messages using ports. A ReceivePort listens for incoming messages, and a SendPort sends messages to a specific receive port. Typically, the main isolate creates a receive port and sends its send port to the child isolate.

DARTRead-only
1
import 'dart:isolate';

void worker(SendPort sendPort) {
  sendPort.send('Hello from isolate');
}

void main() async {
  // Create a ReceivePort for the main isolate
  ReceivePort receivePort = ReceivePort();

  // Spawn the isolate, passing the send port of the receive port
  await Isolate.spawn(worker, receivePort.sendPort);

  // Listen for messages from the isolate
  receivePort.listen((message) {
    print('Received: $message');
    receivePort.close();
  });
}

Two‑Way Communication

Often you need the child isolate to send data back. You can set up a dedicated receive port in the child and send its send port to the main isolate. The main isolate then uses that send port to send messages to the child.

DARTRead-only
1
import 'dart:isolate';

void worker(SendPort mainSendPort) async {
  // Create a receive port for this isolate
  ReceivePort childReceivePort = ReceivePort();
  // Send its send port back to the main isolate
  mainSendPort.send(childReceivePort.sendPort);

  // Now listen for messages from the main isolate
  childReceivePort.listen((message) {
    print('Child received: $message');
    if (message == 'ping') {
      mainSendPort.send('pong');
    }
  });
}

void main() async {
  ReceivePort mainReceivePort = ReceivePort();

  await Isolate.spawn(worker, mainReceivePort.sendPort);

  // Wait for the child to send its send port
  SendPort childSendPort = await mainReceivePort.first;

  // Now we can send messages to the child
  childSendPort.send('ping');

  // Listen for responses
  mainReceivePort.listen((message) {
    if (message != childSendPort) {
      print('Main received: $message');
    }
  });
}

Long‑Lived Isolates and Streaming

You can keep an isolate alive for multiple tasks. The pattern above sets up a permanent communication channel. For simpler one‑off tasks, you can create a short‑lived isolate that exits after sending a result.

The compute() Function (Flutter)

In Flutter, the compute() function provides a convenient way to run a function in a separate isolate and get a single result. It's ideal for offloading a one‑time heavy task.

DARTRead-only
1
import 'package:flutter/foundation.dart';

int heavySum(int n) {
  int sum = 0;
  for (int i = 0; i < n; i++) sum += i;
  return sum;
}

void main() async {
  int result = await compute(heavySum, 1000000);
  print('Sum: $result');
}

Note: compute is Flutter‑specific; it wraps isolate creation and communication for you.

What Can Be Sent Between Isolates?

Messages must be serializable. You can send primitive types, lists, maps, strings, and custom objects if they are serializable (e.g., using dart:convert). You cannot send objects with native resources (like file handles) or closures.

Common Pitfalls

    • Forgetting to close ports – unused ports prevent garbage collection.
    • Sending unsendable objects – leads to runtime errors.
    • Main isolate blocking – isolates help, but if the main isolate is blocked, the UI freezes anyway.
    • Too many isolates – spawning isolates has overhead; reuse them if you have many tasks.

Key Takeaways

    • Isolates are independent workers with their own memory and event loop.
    • Use Isolate.spawn to create a new isolate.
    • Communication is via SendPort and ReceivePort.
    • Use isolates for CPU‑intensive tasks to keep the UI responsive.
    • In Flutter, compute simplifies one‑off isolate tasks.
    • Always close ports when done to avoid leaks.

Test Your Knowledge

Q1
of 4

What is the primary way isolates communicate?

A
Shared memory
B
Message passing via ports
C
Global variables
D
File I/O
Q2
of 4

Which function in Flutter simplifies running a function in an isolate?

A
Isolate.run()
B
compute()
C
spawn()
D
isolate()
Q3
of 4

What happens if you send an unsendable object to an isolate?

A
It is automatically serialized
B
It causes a runtime error
C
It works but may be slow
D
The isolate crashes silently
Q4
of 4

How many isolates can a Dart program have?

A
Only one
B
Two (main and child)
C
As many as system resources allow
D
Limited to 64

Frequently Asked Questions

What is the difference between an isolate and a thread?

Threads share memory and require synchronization (locks). Isolates do not share memory; they communicate only via message passing. This eliminates data races and makes concurrency simpler.

Can multiple isolates run at the same time?

Yes, on multi‑core devices, isolates can run in true parallel. On a single‑core device, they are still concurrent but time‑sliced.

How many isolates can I spawn?

There is no hard limit, but each isolate has its own memory overhead. Spawning hundreds might be impractical. For many tasks, consider using a pool of isolates.

Can I access the same file from two isolates?

Yes, but you must coordinate access because isolates don't share memory. You could use a single isolate to manage file I/O and send file contents via messages.

What is the difference between `Isolate.spawn` and `Isolate.spawnUri`?

Isolate.spawn creates a new isolate from the same code. Isolate.spawnUri creates an isolate from a different Dart file (or even a different URI).

Do isolates have their own global variables?

Yes, each isolate has its own global namespace. Static variables are not shared.

Previous

dart stream

Next

dart event loop

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.