DMarlon Studio

Lesson 17 · 17 of 20

Isolates with Isolate.run · Dart

Move heavy computation off Dart's main thread with Isolate.run, run several isolates in parallel and catch their errors.

Dart · Isolates with Isolate.run in DMarlon Studio: the example's code and its output in the terminal

All Dart code runs inside an isolate: a unit of execution with its own memory and its own event loop. async/await lets you wait without blocking, but it does not spread the computation: if a function spends a second crunching numbers, the main isolate is busy for that second and, in a Flutter app, the UI freezes.

countPrimes is pure CPU work: a sieve of Eratosthenes that marks composite numbers in a list of booleans and counts the rest. `Isolate.run`, from dart:isolate, spawns a new isolate, runs the given function there, returns the result as a Future and shuts the isolate down. Meanwhile, the main isolate stays free.

Several Isolate.run calls at once make use of several cores. They may finish in any order, but Future.wait returns the results in list order and the program prints them at the end, so the output is always the same. Isolates share no memory: whatever is sent and returned is copied, so there are no race conditions to worry about.

If the function fails inside the isolate, the error travels back to the caller: countPrimes(-1) tries to create a list of negative length, throws a RangeError, and the try/catch in main catches it. In Flutter, the compute() function does exactly this, and it is how you process a huge JSON file or an image without the screen stalling.

Code · main.dart

import 'dart:isolate';// CPU-heavy work: count the primes below [limit] with a sieve.int countPrimes(int limit) {  final composite = List<bool>.filled(limit, false);  var count = 0;  for (var n = 2; n < limit; n++) {    if (composite[n]) continue;    count++;    for (var m = n * n; m < limit; m += n) composite[m] = true;  }  return count;}Future<void> main() async {  // Isolate.run runs the function in another isolate (another core)  // and returns its result as a Future: main stays free meanwhile.  final primes = await Isolate.run(() => countPrimes(5000000));  print('Primes below 5,000,000: $primes');  // Several isolates at once; Future.wait keeps the order of the list.  final limits = [1000, 100000, 1000000];  final counts = await Future.wait([for (final n in limits) Isolate.run(() => countPrimes(n))]);  for (var i = 0; i < limits.length; i++) {    print('Below ${limits[i]}: ${counts[i]}');  }  // An exception inside the isolate comes back to the caller.  try {    await Isolate.run(() => countPrimes(-1));  } on RangeError {    print('The worker failed with a RangeError');  }}

Output · dart run main.dart

Primes below 5,000,000: 348513
Below 1000: 168
Below 100000: 9592
Below 1000000: 78498
The worker failed with a RangeError

Key points

  • Each isolate has its own memory: no shared variables.
  • async does not spread computation; another isolate does.
  • Isolate.run runs a function in another isolate and returns a Future.
  • With Future.wait, results follow the order of the list.
  • Errors from the isolate are caught with try/catch by the caller.

Exercise

Write a sumOfSquares(int n) function that adds the squares from 1 to n, and run n = 1000 and n = 1000000 in parallel with Isolate.run.