Isolates and parallelism

Dart · Expert · 14 min

Isolates are Dart's only way to use multiple cores and keep a Flutter UI at 60+ fps during heavy work.

Dart code runs in isolates: each has its own memory heap and event loop, and isolates share no mutable state. That's why Dart needs no locks — and why parallelism requires message passing.

The simplest API is `Isolate.run` (Dart 2.19+):

final result = await Isolate.run(() => parseHugeJson(text));

It spawns an isolate, runs the function, returns its result and shuts the isolate down. Flutter's compute(fn, arg) is a wrapper around the same idea. Use it for work that takes more than a few milliseconds: JSON decoding of large payloads, image processing, encryption, search indexing.

For long-lived workers, use Isolate.spawn with a `SendPort`/`ReceivePort` pair: the worker sends back its own SendPort, and both sides exchange messages.

What can be sent: most objects are copied (deep copy), including lists, maps, records and instances of your classes. Exceptions: objects holding native resources such as Socket, ReceivePort or DynamicLibrary can't be sent. Closures can be sent only if what they capture is sendable — capturing a big object by accident copies it too. Isolate.exit transfers the result without copying when ending a worker; TransferableTypedData moves byte buffers cheaply.

Isolates spawned from the same isolate group share code and start quickly (tens of microseconds to milliseconds). On the web, isolates aren't available in the same form; use web workers.

Example

import 'dart:isolate';

int countPrimes(int limit) {
  var count = 0;
  for (var n = 2; n < limit; n++) {
    var prime = true;
    for (var d = 2; d * d <= n; d++) {
      if (n % d == 0) { prime = false; break; }
    }
    if (prime) count++;
  }
  return count;
}

Future<void> main() async {
  final sw = Stopwatch()..start();
  final results = await Future.wait([
    Isolate.run(() => countPrimes(2000000)),
    Isolate.run(() => countPrimes(1000000)),
  ]);
  print(results);                 // [148933, 78498] — computed in parallel
  print('main isolate stayed free: ${sw.elapsedMilliseconds} ms');
}

Remember

  • Isolates have separate heaps and communicate only by messages
  • Isolate.run (or Flutter's compute) offloads one CPU-heavy task
  • Messages are copied; native resources can't be sent

Watch out for

  • Capturing a large object in the closure passed to Isolate.run, silently copying it to the new isolate.

Check yourself

  1. Why doesn't Dart need mutexes for ordinary objects shared between isolates?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: dart.dev/language/isolates

Builds on

Unlocks

Nothing builds on this yet.