Watch the video (15 min)
On this page22 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Starting a Worker with Isolate.spawn
  5. 2. One Argument, Many Values
  6. 3. Many Messages — and Closing the Port
  7. 4. Two-Way Communication
  8. 5. A Long-Lived Worker
  9. 6. Stopping a Worker Cleanly
  10. 7. Errors in a Spawned Isolate: onError
  11. 8. Errors from Isolate.run Are Rethrown
  12. 9. Several Isolates in Parallel
  13. 10. A Real-World Example
  14. Code Examples
  15. Common Beginner Mistakes
  16. Best Practices
  17. Mini Challenge
  18. Practice Questions
  19. Practice Answers
  20. Quick Revision
  21. Key Takeaways
  22. What's Next?

Lecture 99 — Working with Isolates

Category16 — Concurrency & Isolates
Lecture099 of 140
Estimated video length15 min
TargetDart 3.12.2
LevelIntermediate

Isolate.run starts a worker, gets one answer, and throws the worker away. With Isolate.spawn and ports you keep a worker alive, talk to it in both directions, and give it job after job.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every output and error message in this lecture is real. Each one was produced by running the code shown on Dart 3.12.2. Timing lines such as Parallel: 873 ms change on every run and every computer — read them as "about 1 second".


Prerequisites

Students should already understand, from Lectures 076–098:

  • records, destructuring, and list patterns like var [a, b] = list
  • async/await, try/catch, and Future.wait (Lectures 089–091)
  • streams, await for, .first, and listen (Lectures 092–096)
  • isolates, Isolate.run, and why messages are copied between isolates (Lecture 098)

Connection With Previous Lectures

In Lecture 098 we moved heavy work off the main isolate with one line:

dart
var total = await Isolate.run(() => slowSum(1000000000));

That is perfect for one job. But Sara's banking app checks transactions as they arrive. A new isolate per transaction wastes time, and a fresh isolate forgets everything — it cannot keep a running balance. We need a worker that stays alive, receives jobs, answers, and shuts down cleanly.


Learning Objectives

By the end of this lecture you will be able to:

  • Start an isolate with Isolate.spawn and an entry function
  • Send messages back to the main isolate through a ReceivePort and its SendPort
  • Pass several values into a worker by packing them in a record
  • Set up two-way communication by sending a SendPort back
  • Build a long-lived worker that handles many jobs
  • Close ports and kill isolates so your program always exits
  • Catch errors from spawned isolates and from Isolate.run
  • Run several isolates in parallel with Future.wait

1. Starting a Worker with Isolate.spawn

Isolate.spawn starts a new isolate that runs an entry function — a top-level function with exactly one parameter. The worker cannot return a value to us, so we give it a SendPort: an address it can send messages to.

dart
import 'dart:isolate';

void addMarks(SendPort replyTo) {
  var total = 80 + 75 + 85;
  replyTo.send(total);
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(addMarks, port.sendPort);
  var total = await port.first;
  print('Total from worker: $total');
}
output
Total from worker: 240

Analogy — a ReceivePort is your mailbox and its sendPort is the address written on it. You hand the address to the worker; the worker posts letters, and only you can open the mailbox.

A ReceivePort is a stream of incoming messages. await port.first waits for one message and then closes the port.


2. One Argument, Many Values

The entry function takes a single argument. To send in data and a reply address, pack them into a record:

dart
import 'dart:isolate';

void averageWorker((List<int>, SendPort) job) {
  var (marks, replyTo) = job;
  var total = marks.reduce((a, b) => a + b);
  replyTo.send(total / marks.length);
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(averageWorker, ([90, 88, 92], port.sendPort));
  print('Sara\'s average: ${await port.first}');
}
output
Sara's average: 90.0

The list is copied into the worker (Lecture 098).


3. Many Messages — and Closing the Port

A worker can send as many messages as it likes. Agree on a "done" signal (here null) and close the port when it arrives:

dart
import 'dart:isolate';

void countDown(SendPort replyTo) {
  for (var i = 3; i >= 1; i--) {
    replyTo.send(i);
  }
  replyTo.send(null); // "I'm done"
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(countDown, port.sendPort);
  await for (var message in port) {
    if (message == null) {
      port.close();
    } else {
      print('Received $message');
    }
  }
  print('Port closed, program ends');
}
output
Received 3
Received 2
Received 1
Port closed, program ends

An open ReceivePort in the main isolate keeps the program running. Forget port.close() and this program never ends — await for waits for mail that never comes.


4. Two-Way Communication

To send jobs to the worker, the worker needs its own mailbox. It creates a ReceivePort and sends that port's SendPort back — the "handshake". Each job then carries its own reply address:

dart
import 'dart:isolate';

void doubler(SendPort mainPort) async {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort); // 1. tell main where to send jobs
  var (number, replyTo) = await inbox.first as (int, SendPort);
  replyTo.send(number * 2); // 3. answer on the reply port
}

void main() async {
  var setup = ReceivePort();
  await Isolate.spawn(doubler, setup.sendPort);
  var workerPort = await setup.first as SendPort;

  var reply = ReceivePort();
  workerPort.send((21, reply.sendPort)); // 2. a job plus a reply address
  print('Answer: ${await reply.first}');
}
output
Answer: 42

Messages arrive untyped, so as states what we expect. A fresh reply port per job means every answer lands in the right place.


5. A Long-Lived Worker

Now the worker uses listen instead of .first, so it keeps answering. A small ask function hides the reply-port details:

dart
import 'dart:isolate';

void squarer(SendPort mainPort) {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  inbox.listen((message) {
    var (number, replyTo) = message as (int, SendPort);
    print('worker: squaring $number');
    replyTo.send(number * number);
  });
}

Future<int> ask(SendPort worker, int number) async {
  var reply = ReceivePort();
  worker.send((number, reply.sendPort));
  return await reply.first as int;
}

void main() async {
  var setup = ReceivePort();
  var isolate = await Isolate.spawn(squarer, setup.sendPort);
  var worker = await setup.first as SendPort;

  for (var n in [2, 3, 4]) {
    print('$n squared is ${await ask(worker, n)}');
  }
  isolate.kill();
}
output
worker: squaring 2
2 squared is 4
worker: squaring 3
3 squared is 9
worker: squaring 4
4 squared is 16

One isolate, three jobs. Isolate.spawn returns an Isolate; isolate.kill() shuts the worker down.


6. Stopping a Worker Cleanly

kill stops a worker from outside. The polite way is a "stop" message: the worker closes its inbox, has nothing left to do, and exits. onExit tells us when it is gone:

dart
import 'dart:isolate';

void squarer(SendPort mainPort) {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  inbox.listen((message) {
    if (message == 'stop') {
      print('worker: closing my inbox');
      inbox.close();
      return;
    }
    var (number, replyTo) = message as (int, SendPort);
    replyTo.send(number * number);
  });
}

void main() async {
  var setup = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(squarer, setup.sendPort, onExit: exit.sendPort);
  var worker = await setup.first as SendPort;

  var reply = ReceivePort();
  worker.send((5, reply.sendPort));
  print('5 squared is ${await reply.first}');

  worker.send('stop');
  await exit.first;
  print('Worker has exited');
}
output
5 squared is 25
worker: closing my inbox
Worker has exited

When the worker exits, its onExit port receives null. A worker busy in a loop never reads its messages; stop it with isolate.kill(priority: Isolate.immediate) (Example 5). Workers still alive when the main isolate finishes are shut down with it.


7. Errors in a Spawned Isolate: onError

An uncaught error in a spawned isolate kills that isolate — and the main isolate is not told unless it asks. Pass an onError port: each error arrives as a two-item list, the message and the stack trace, both as strings.

dart
import 'dart:isolate';

void checkMarks(int marks) {
  if (marks > 100) {
    throw ArgumentError('Marks cannot be above 100: $marks');
  }
  print('worker: $marks is fine');
}

void main() async {
  var errors = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(
    checkMarks,
    120,
    onError: errors.sendPort,
    onExit: exit.sendPort,
  );
  var [message, _] = await errors.first as List;
  print('Worker failed: $message');
  await exit.first;
  print('Worker is gone, main carries on');
}
output
Worker failed: Invalid argument(s): Marks cannot be above 100: 120
Worker is gone, main carries on

The error is only text now — you cannot catch it with on ArgumentError.


8. Errors from Isolate.run Are Rethrown

Isolate.run does all of this for you: if the function throws, the same error is thrown again from the await, so an ordinary try/catch works:

dart
import 'dart:isolate';

double average(List<int> marks) {
  if (marks.isEmpty) throw StateError('No marks to average');
  return marks.reduce((a, b) => a + b) / marks.length;
}

void main() async {
  try {
    var result = await Isolate.run(() => average([]));
    print(result);
  } on StateError catch (e) {
    print('Caught in main: ${e.message}');
  }
}
output
Caught in main: No marks to average

Prefer Isolate.run for one-off jobs; keep Isolate.spawn for long-lived workers.


9. Several Isolates in Parallel

Each isolate can run on its own CPU core. Isolate.run returns a future, so Future.wait from Lecture 091 runs several of them at the same time:

dart
import 'dart:isolate';

int slowSum(int limit) {
  var sum = 0;
  for (var i = 0; i < limit; i++) {
    sum += i % 7;
  }
  return sum;
}

void main() async {
  const limit = 1000000000;
  var stopwatch = Stopwatch()..start();
  slowSum(limit);
  slowSum(limit);
  slowSum(limit);
  print('Sequential: ${stopwatch.elapsedMilliseconds} ms');

  stopwatch.reset();
  var sums = await Future.wait([
    Isolate.run(() => slowSum(limit)),
    Isolate.run(() => slowSum(limit)),
    Isolate.run(() => slowSum(limit)),
  ]);
  print('Parallel: ${stopwatch.elapsedMilliseconds} ms');
  print(sums);
}
output
Sequential: 3105 ms
Parallel: 873 ms
[2999999997, 2999999997, 2999999997]

Three sums in about the time of one — as long as your computer has spare CPU cores.


10. A Real-World Example

A long-lived worker builds student reports. Three requests go out together; then the worker is stopped cleanly:

dart
import 'dart:isolate';

void reportWorker(SendPort mainPort) {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  inbox.listen((message) {
    if (message == 'stop') {
      inbox.close();
      return;
    }
    var (name, marks, replyTo) = message as (String, List<int>, SendPort);
    var total = marks.reduce((a, b) => a + b);
    replyTo.send('$name: total $total, average ${total / marks.length}');
  });
}

Future<String> askReport(SendPort worker, String name, List<int> marks) async {
  var reply = ReceivePort();
  worker.send((name, marks, reply.sendPort));
  return await reply.first as String;
}

void main() async {
  var setup = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(reportWorker, setup.sendPort, onExit: exit.sendPort);
  var worker = await setup.first as SendPort;

  var reports = await Future.wait([
    askReport(worker, 'Ali', [80, 75, 85]),
    askReport(worker, 'Sara', [90, 88, 92]),
    askReport(worker, 'Zayan', [70, 65, 72]),
  ]);
  reports.forEach(print);

  worker.send('stop');
  await exit.first;
  print('Report worker stopped');
}
output
Ali: total 240, average 80.0
Sara: total 270, average 90.0
Zayan: total 207, average 69.0
Report worker stopped

Handshake, reply ports, a stop message, and onExit — a worker's whole lifecycle.


Code Examples

Example 1 — One Message Back

dart
import 'dart:isolate';

void greet(SendPort replyTo) {
  replyTo.send('Hello from another isolate!');
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(greet, port.sendPort);
  print(await port.first);
}
output
Hello from another isolate!

Example 2 — Data In, Answer Out

dart
import 'dart:isolate';

void sumWorker((List<int>, SendPort) job) {
  var (numbers, replyTo) = job;
  replyTo.send(numbers.reduce((a, b) => a + b));
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(sumWorker, ([4, 5, 6], port.sendPort));
  print('Sum: ${await port.first}');
}
output
Sum: 15

Example 3 — A Stream of Names

dart
import 'dart:isolate';

void names(SendPort replyTo) {
  for (var name in ['Ali', 'Sara', 'Zayan']) {
    replyTo.send(name);
  }
  replyTo.send(null);
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(names, port.sendPort);
  await for (var name in port) {
    if (name == null) break;
    print('Hello, $name');
  }
}
output
Hello, Ali
Hello, Sara
Hello, Zayan

break cancels the await for subscription, which also closes the port.

Example 4 — The Handshake

dart
import 'dart:isolate';

void shouter(SendPort mainPort) async {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  var (text, replyTo) = await inbox.first as (String, SendPort);
  replyTo.send(text.toUpperCase());
}

void main() async {
  var setup = ReceivePort();
  await Isolate.spawn(shouter, setup.sendPort);
  var worker = await setup.first as SendPort;
  var reply = ReceivePort();
  worker.send(('hello sara', reply.sendPort));
  print(await reply.first);
}
output
HELLO SARA

Example 5 — Killing a Busy Worker

dart
import 'dart:isolate';

void forever(String name) async {
  while (true) {
    print('$name is still working...');
    await Future.delayed(Duration(milliseconds: 400));
  }
}

void main() async {
  var exit = ReceivePort();
  var isolate = await Isolate.spawn(forever, 'Zayan', onExit: exit.sendPort);
  await Future.delayed(Duration(seconds: 1));
  isolate.kill(priority: Isolate.immediate);
  await exit.first;
  print('Worker killed');
}
output
Zayan is still working...
Zayan is still working...
Zayan is still working...
Worker killed

Example 6 — Catching an Isolate.run Error

dart
import 'dart:isolate';

void main() async {
  try {
    await Isolate.run(() => int.parse('abc'));
  } on FormatException catch (e) {
    print('Caught: ${e.message}');
  }
}
output
Caught: Invalid radix-10 number

Example 7 — Student Averages in Parallel

dart
import 'dart:isolate';

double average(List<int> marks) => marks.reduce((a, b) => a + b) / marks.length;

void main() async {
  var averages = await Future.wait([
    Isolate.run(() => average([80, 75, 85])),
    Isolate.run(() => average([90, 88, 92])),
    Isolate.run(() => average([70, 65, 72])),
  ]);
  print('Averages: $averages');
}
output
Averages: [80.0, 90.0, 69.0]

Example 8 — Shopping Total

dart
import 'dart:isolate';

void cartWorker((Map<String, double>, SendPort) job) {
  var (cart, replyTo) = job;
  var total = cart.values.fold(0.0, (sum, price) => sum + price);
  replyTo.send(total);
}

void main() async {
  var port = ReceivePort();
  var cart = {'Notebook': 500.0, 'Pen': 50.0, 'Bag': 1500.0};
  await Isolate.spawn(cartWorker, (cart, port.sendPort));
  print('Total: ${await port.first}');
}
output
Total: 2050.0

Example 9 — A Bank Worker That Remembers

dart
import 'dart:isolate';

void bankWorker(SendPort mainPort) {
  var balance = 1000.0; // lives as long as the worker
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  inbox.listen((message) {
    var (amount, replyTo) = message as (double, SendPort);
    balance += amount;
    replyTo.send(balance);
  });
}

Future<double> apply(SendPort worker, double amount) async {
  var reply = ReceivePort();
  worker.send((amount, reply.sendPort));
  return await reply.first as double;
}

void main() async {
  var setup = ReceivePort();
  var isolate = await Isolate.spawn(bankWorker, setup.sendPort);
  var worker = await setup.first as SendPort;
  for (var amount in [-250.0, 500.0, -100.0]) {
    print('After $amount: ${await apply(worker, amount)}');
  }
  isolate.kill();
}
output
After -250.0: 750.0
After 500.0: 1250.0
After -100.0: 1150.0

Isolate.run could never do this: each call starts from scratch.

Example 10 — A Faulty Temperature Sensor

dart
import 'dart:isolate';

void sensor(double reading) {
  if (reading < -273.15) throw StateError('Impossible reading: $reading');
  print('Reading OK: $reading');
}

void main() async {
  var errors = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(
    sensor,
    -300.0,
    onError: errors.sendPort,
    onExit: exit.sendPort,
  );
  var [message, _] = await errors.first as List;
  print('Sensor error: $message');
  await exit.first;
}
output
Sensor error: Bad state: Impossible reading: -300.0

Common Beginner Mistakes

1. Never closing a ReceivePort

dart
void main() async {
  var port = ReceivePort();
  await Isolate.spawn(work, port.sendPort); // work sends 42
  port.listen((m) => print('Got $m'));
  // ✗ the port stays open
}
output
Got 42

No error — the program just never ends (stop it with Ctrl+C). Close the port after the last message, or use .first, which closes it for you.

2. Reading the same ReceivePort twice

dart
void main() async {
  var port = ReceivePort();
  await Isolate.spawn(work, port.sendPort); // work sends 1, then 2
  print(await port.first);
  // print(await port.first); // ✗
}
output
Bad state: Stream has already been listened to.

A ReceivePort is a single-subscription stream, and .first already used it. Use one await for for many messages, or a reply port per job (Step 5).

3. An entry function with two parameters

dart
void add(int a, int b) => print(a + b);

void main() async {
  // await Isolate.spawn(add, 2); // ✗
}
output
Error: The argument type 'void Function(int, int)' can't be assigned to the parameter type 'void Function(int)'.

The entry function takes exactly one argument. Pack several values into a record (Step 2).

4. Waiting for an answer from a worker that crashed

dart
void boom(SendPort replyTo) => throw StateError('bad');

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(boom, port.sendPort);
  // var result = await port.first; // ✗ waits forever
}
output
Unhandled exception:
Bad state: bad

The worker dies, but main waits forever for its reply. Pass onError and onExit ports (Step 7), or use Isolate.run.


Best Practices

  • Use Isolate.run for one job; use Isolate.spawn when a worker should stay alive or remember state.
  • Give each job its own reply port.
  • Close every ReceivePort you create — .first and break do it for you.
  • Stop workers with a "stop" message, kill(), or kill(priority: Isolate.immediate), and use onExit to know when they are gone.
  • Always pass onError to Isolate.spawn when main waits for a worker's answer.
  • Run independent isolates together with Future.wait, but no more than you have CPU cores.

Mini Challenge

Zayan's weather station needs a long-lived converter worker. Send it the Celsius readings 25.0, 30.0, and -5.0 one at a time, print each Fahrenheit answer (celsius * 9 / 5 + 32), then send 'stop' and wait for the worker to exit.

Try it yourself before opening the solution.

Show solution
dart
import 'dart:isolate';

void converter(SendPort mainPort) {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  inbox.listen((message) {
    if (message == 'stop') {
      inbox.close();
      return;
    }
    var (celsius, replyTo) = message as (double, SendPort);
    replyTo.send(celsius * 9 / 5 + 32);
  });
}

Future<double> convert(SendPort worker, double celsius) async {
  var reply = ReceivePort();
  worker.send((celsius, reply.sendPort));
  return await reply.first as double;
}

void main() async {
  var setup = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(converter, setup.sendPort, onExit: exit.sendPort);
  var worker = await setup.first as SendPort;
  for (var c in [25.0, 30.0, -5.0]) {
    print('$c°C = ${await convert(worker, c)}°F');
  }
  worker.send('stop');
  await exit.first;
  print('Converter stopped');
}
output
25.0°C = 77.0°F
30.0°C = 86.0°F
-5.0°C = 23.0°F
Converter stopped

Practice Questions

Solve each one before looking at the answers.

Beginner

  1. Spawn a worker that sends back the text 'Ready', and print it.
  2. Spawn a worker with the record (10, port.sendPort) that sends back the number times 3.
  3. Use Isolate.run to add the numbers 1 to 100 and print the result.

Application

  1. A worker sends 'A', 'B', 'C', then null. Print each letter with await for and make sure the program ends.

  2. Why does this crash, and how would you fix it?

    dart
    var port = ReceivePort();
    await Isolate.spawn(work, port.sendPort);
    print(await port.first);
    print(await port.first);
  3. Use a handshake so main sends 'Sara' to the worker and the worker replies 'Welcome, Sara!'.

  4. Run () => throw Exception('Disk full') with Isolate.run and print Caught: plus the error.

Challenge

  1. Build a long-lived counter worker that keeps a running total. Send 10, 20, 30 and print 10, 30, 60; then stop it with 'stop' and wait for onExit.
  2. With Future.wait and Isolate.run, compute the sum of 1..n for n = 10, 100, 1000, and 10000 in parallel and print the list.
  3. Spawn a worker that throws StateError('Negative balance') when given -50.0. Catch it through onError, print Worker failed: ..., then print Done after the worker exits.

Practice Answers

Question 1 — Solution

dart
import 'dart:isolate';

void ready(SendPort replyTo) => replyTo.send('Ready');

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(ready, port.sendPort);
  print(await port.first);
}
output
Ready

Question 2 — Solution

dart
import 'dart:isolate';

void triple((int, SendPort) job) {
  var (number, replyTo) = job;
  replyTo.send(number * 3);
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(triple, (10, port.sendPort));
  print(await port.first);
}
output
30

Question 3 — Solution

dart
import 'dart:isolate';

void main() async {
  var sum = await Isolate.run(
    () => List.generate(100, (i) => i + 1).reduce((a, b) => a + b),
  );
  print(sum);
}
output
5050

Question 4 — Solution

dart
import 'dart:isolate';

void letters(SendPort replyTo) {
  for (var letter in ['A', 'B', 'C']) {
    replyTo.send(letter);
  }
  replyTo.send(null);
}

void main() async {
  var port = ReceivePort();
  await Isolate.spawn(letters, port.sendPort);
  await for (var letter in port) {
    if (letter == null) break;
    print(letter);
  }
}
output
A
B
C

Question 5 — Solution

The first .first already listened to the port (and closed it), so the second throws Bad state: Stream has already been listened to. Read both with one await for, closing the port after the second.

Question 6 — Solution

dart
import 'dart:isolate';

void welcomer(SendPort mainPort) async {
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  var (name, replyTo) = await inbox.first as (String, SendPort);
  replyTo.send('Welcome, $name!');
}

void main() async {
  var setup = ReceivePort();
  await Isolate.spawn(welcomer, setup.sendPort);
  var worker = await setup.first as SendPort;
  var reply = ReceivePort();
  worker.send(('Sara', reply.sendPort));
  print(await reply.first);
}
output
Welcome, Sara!

Question 7 — Solution

dart
import 'dart:isolate';

void main() async {
  try {
    await Isolate.run(() => throw Exception('Disk full'));
  } catch (e) {
    print('Caught: $e');
  }
}
output
Caught: Exception: Disk full

Question 8 — Solution

dart
import 'dart:isolate';

void counter(SendPort mainPort) {
  var total = 0;
  var inbox = ReceivePort();
  mainPort.send(inbox.sendPort);
  inbox.listen((message) {
    if (message == 'stop') {
      inbox.close();
      return;
    }
    var (amount, replyTo) = message as (int, SendPort);
    total += amount;
    replyTo.send(total);
  });
}

Future<int> add(SendPort worker, int amount) async {
  var reply = ReceivePort();
  worker.send((amount, reply.sendPort));
  return await reply.first as int;
}

void main() async {
  var setup = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(counter, setup.sendPort, onExit: exit.sendPort);
  var worker = await setup.first as SendPort;
  for (var n in [10, 20, 30]) {
    print(await add(worker, n));
  }
  worker.send('stop');
  await exit.first;
}
output
10
30
60

Question 9 — Solution

dart
import 'dart:isolate';

int sumTo(int n) {
  var total = 0;
  for (var i = 1; i <= n; i++) {
    total += i;
  }
  return total;
}

void main() async {
  var sums = await Future.wait([
    for (var n in [10, 100, 1000, 10000]) Isolate.run(() => sumTo(n)),
  ]);
  print(sums);
}
output
[55, 5050, 500500, 50005000]

Question 10 — Solution

dart
import 'dart:isolate';

void withdraw(double amount) {
  if (amount < 0) throw StateError('Negative balance');
  print('Withdrew $amount');
}

void main() async {
  var errors = ReceivePort();
  var exit = ReceivePort();
  await Isolate.spawn(
    withdraw,
    -50.0,
    onError: errors.sendPort,
    onExit: exit.sendPort,
  );
  var [message, _] = await errors.first as List;
  print('Worker failed: $message');
  await exit.first;
  print('Done');
}
output
Worker failed: Bad state: Negative balance
Done

Quick Revision

  1. How many parameters does an entry function for Isolate.spawn take?
  2. What is the difference between a ReceivePort and a SendPort?
  3. Why does an open ReceivePort keep a program running?
  4. How does a worker let main send it jobs?
  5. Name two ways to stop a long-lived worker.
  6. What does the onExit port receive?
  7. How does an error from Isolate.spawn reach main, compared with Isolate.run?
  8. When does running isolates with Future.wait make a program faster?

Key Takeaways

  • Isolate.spawn(entry, argument) starts a worker; the entry function takes one argument.
  • A ReceivePort is a stream of messages; its sendPort is the address other isolates send to.
  • Pack data and a reply SendPort into a record to send several values.
  • Two-way communication: the worker sends its own SendPort back in a handshake.
  • A worker that listens keeps handling jobs and can remember state between them.
  • Close ports, send 'stop', or kill() a worker; onExit tells you when it is gone.
  • Spawned errors arrive as text on the onError port; Isolate.run rethrows the real error.
  • Future.wait over several Isolate.run calls uses several CPU cores at once.

What's Next?

Lecture 100 — Concurrency Best Practices. You now have every tool: futures, streams, Isolate.run, and long-lived workers. Next we learn which one to pick for each job and how to keep the main isolate responsive.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.