Isolates
By Syncfusion®, Inc. · Last updated
How isolates run heavy work in the background with their own memory: Isolate.run for one job, Isolate.spawn with ReceivePort and SendPort for messages, and closing, killing and error listeners.
What Is an Isolate?
Isolate = a separate worker that runs Dart code at the same time as your main program, with its own memory.
- Your
main()already runs in one isolate: the main isolate. - You can start more isolates for heavy work, like adding up a million numbers or reading a huge file.
- Isolates do not share memory. They talk only by sending messages.
Simple lesson: an isolate is a helper who works in a separate room, and you pass notes under the door.
A Real-World Example
Report card day at school.
- The class teacher must also keep teaching. She cannot stop for three hours to add up marks.
- So she gives the mark sheets to a helper in the staff room.
- The helper works with their own copy of the sheets, not hers.
- When done, the helper sends back a note: "Class average: 78.4".
- The teacher's class never stopped. That is what isolates do for your app.
On your computer, isolates run truly at the same time, on different processor cores.
The course runner simulates isolates on one thread, so you can practise the code. Results and message order are the same, but there is no real speed-up. On the web, Dart cannot use Isolate.spawn at all.
What You Will Learn
In this lesson, you will learn:
- What isolates are, and why they have separate memory.
- How isolates differ from threads in other languages.
- How to run one job with
Isolate.run(). - How to send messages with
Isolate.spawn(),ReceivePortandSendPort. - How to close ports, kill isolates, and listen for errors.
Isolates and Memory
Memory Isolation
Memory isolation = each isolate has its own variables. One isolate cannot read or change another's.
- When you send a value to another isolate, it gets a copy.
- Changing the copy does not change the original.
The worker changes only its copy
import 'dart:isolate';
void addMark(List<Object> message) {
var marks = message[0] as List<int>;
var reply = message[1] as SendPort;
marks.add(100);
reply.send(marks.length);
}
Future<void> main() async {
var marks = [70, 80, 90];
var inbox = ReceivePort();
await Isolate.spawn(addMark, [marks, inbox.sendPort]);
var workerCount = await inbox.first;
print('Worker saw $workerCount marks');
print('Main still has ${marks.length} marks');
inbox.close();
}
You will learn Isolate.spawn, ReceivePort and SendPort below. For now, see the result: the worker added a mark to its list only.
Isolates vs Threads
| Threads (Java, C++) | Dart isolates | |
|---|---|---|
| Memory | shared by all threads | separate for each isolate |
| Talking | change shared variables | send messages |
| Common bugs | two threads change one value at once | almost none of these |
| Cost | cheap to share data | data is copied in messages |
Because nothing is shared, two isolates can never fight over one variable. That makes isolates safe.
Simple lesson: isolates are safe because each one has its own memory, and they only exchange copies.
Isolate.run()
Isolate.run(function) = the simplest way. It starts an isolate, runs one function, returns its result as a Future, and closes the isolate.
Adding up big numbers in the background
import 'dart:isolate';
int sumUpTo(int n) {
var total = 0;
for (var i = 1; i <= n; i++) {
total += i;
}
return total;
}
Future<void> main() async {
print('Starting the heavy job...');
var total = await Isolate.run(() => sumUpTo(1000000));
print('Total: $total');
}
- While the worker adds, the main isolate stays free. In an app, the screen keeps moving.
- Use
Isolate.runfor most jobs: "do this one thing, give me the answer".
What Isolate.run does
Simple lesson: Isolate.run sends one job to a helper and gives you the answer as a Future.
Isolate.spawn with Ports
For long-running helpers that send many messages, use Isolate.spawn with ports.
ReceivePort= a mailbox. It is also a Stream of the messages that arrive.SendPort= the address of a mailbox. Anyone with it can send messages there.Isolate.spawn(entry, message)= start an isolate runningentry(message).
Messages from the Worker
A worker that reports progress
import 'dart:isolate';
void checkPapers(SendPort mailbox) {
for (var paper = 1; paper <= 3; paper++) {
mailbox.send('Paper $paper checked');
}
mailbox.send('done');
}
Future<void> main() async {
var inbox = ReceivePort();
await Isolate.spawn(checkPapers, inbox.sendPort);
await for (var message in inbox) {
if (message == 'done') {
inbox.close();
} else {
print(message);
}
}
print('All papers checked');
}
maingives the worker the address of its mailbox:inbox.sendPort.- The worker sends notes with
mailbox.send(...). mainreads them withawait for, because aReceivePortis a Stream.close()ends the Stream, so the loop can finish.
Talking Both Ways
To send messages to the worker too, the worker makes its own mailbox and sends its address back first:
A worker that answers questions
import 'dart:isolate';
void squarer(SendPort replyTo) async {
var myInbox = ReceivePort();
replyTo.send(myInbox.sendPort);
await for (var n in myInbox) {
if (n == 0) {
myInbox.close();
} else {
replyTo.send(n * n);
}
}
}
Future<void> main() async {
var inbox = ReceivePort();
await Isolate.spawn(squarer, inbox.sendPort);
SendPort? worker;
var asked = 0;
await for (var msg in inbox) {
if (msg is SendPort) {
worker = msg;
worker.send(4);
asked++;
} else {
print('Answer: $msg');
if (asked < 2) {
worker!.send(9);
asked++;
} else {
worker!.send(0);
inbox.close();
}
}
}
}
Only some values can be sent: numbers, strings, booleans, null, lists, maps, SendPorts, and objects made of them.
Things tied to the current isolate, like an open ReceivePort or a running Stream, cannot be sent.
Simple lesson: a ReceivePort is your mailbox, and you give its SendPort to anyone who should write to you.
Closing, Killing and Errors
| Action | Code | When |
|---|---|---|
| Stop listening | port.close() | you need no more messages |
| Stop a worker | isolate.kill() | the job is no longer needed |
| Hear about errors | Isolate.spawn(f, msg, onError: port.sendPort) | a worker might crash |
When a spawned worker crashes, its error arrives at the onError port as a list: [error text, stack trace text].
Hearing about a worker's crash
import 'dart:isolate';
void badWorker(int n) {
throw StateError('Paper $n is missing');
}
Future<void> main() async {
var errors = ReceivePort();
await Isolate.spawn(badWorker, 7, onError: errors.sendPort);
var report = await errors.first;
print('Worker failed: ${report[0]}');
errors.close();
}
- An open
ReceivePortkeeps the program alive. Close every port you open. - With
Isolate.run, errors are simpler: the worker's exception is thrown at yourawait, so usetryandcatch.
Easy way to remember
- Isolate = a helper in another room, with its own copy of everything.
- Isolate.run = "do this one job and tell me the answer".
- ReceivePort = my mailbox. SendPort = my address.
- close / kill / onError = tidy up, send home, hear about problems.
Simple lesson: close the ports you open, kill workers you no longer need, and listen for their errors.
TL;DR
- Isolate = separate worker, own memory, talks by messages (copies).
- Unlike threads, isolates share nothing, so they cannot fight over a variable.
Isolate.run(() => work())runs one job and gives a Future of the result.Isolate.spawn(entry, sendPort)+ReceivePortfor many messages. AReceivePortis a Stream.close()ports,kill()isolates,onError:to hear crashes. Not available on the web.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Changing a list inside a worker | The main list changes too | The worker has a copy | Main never sees the change. |
Forgetting inbox.close() | Ports close by themselves | Close every port | The program never ends. |
| Using isolates for tiny jobs | More isolates are always faster | Starting one costs time | The app gets slower. |
Sending a ReceivePort | Anything can be sent | Send its sendPort instead | An error. |
Expecting Isolate.spawn on the web | It works everywhere | Use compute in Flutter, or web workers | It fails in the browser. |
Check Yourself
Exercises
What to Remember
- An isolate is a separate worker with its own memory, and isolates talk only by messages.
- Messages are copies, so isolates can never change each other's variables.
Isolate.runis the easy way to run one heavy job in the background.Isolate.spawnwith aReceivePortand itsSendPorthandles many messages.- Close every port, kill unneeded isolates, and use
onError:to hear about crashes.
Next: File Handling. You will read and write files on your computer.