Dart Isolates
Concurrency refers to the ability to handle multiple tasks simultaneously.
Dart's concurrency model differs from traditional multi-threading; it uses Isolates to implement parallel computing.
This chapter introduces Dart's concurrency model, the concept and usage of Isolates, and the message passing mechanism.
* * *
## Dart Concurrency Model
Most programming languages use a shared-memory multi-threading model, where multiple threads share the same memory space.
The drawback of this model is that it easily leads to race conditions and deadlocks.
Dart adopts a different strategy: each Isolate has its own independent memory heap, and Isolates do not share memory with each other.
They communicate through message passing, which fundamentally avoids data race issues.
Dart's concurrency model has three levels:
| Level | Mechanism | Applicable Scenarios |
| --- | --- | --- |
| Event Loop | Single-threaded async (Event Loop) | I/O operations, timers, user interactions |
| Isolate | Independent memory + message passing | CPU-intensive computations |
| Future/Stream | Async programming syntax | Most daily development scenarios |
> Most Dart programs' concurrency needs can be met through async/await and Future/Stream. Isolates are only necessary when you need to perform a large amount of CPU-intensive computation. Introducing Isolates prematurely will make the code more complex with little benefit.
* * *
## Isolate Concept and Usage
Isolate is Dart's concurrency unit, each Isolate has its own independent memory and event loop.
The main program itself runs in an Isolate (the main Isolate).
### Using Isolate.spawn to Create a New Isolate
## Example
import'dart:isolate';
// This function will run in the new Isolate
// SendPort is used to send messages to the main Isolate
void heavyComputation(SendPort sendPort){
print('New Isolate starting computation...');
// Simulate CPU-intensive computation
int sum =0;
for(int i =1; i <=10000000; i++){
sum += i;
}
// Send the computation result back to the main Isolate
sendPort.send(sum);
print('New Isolate computation complete, result sent');
}
Future main() async {
print('Main Isolate started');
// Create a ReceivePort to receive messages
var receivePort = ReceivePort();
// spawn creates a new Isolate
await Isolate.spawn(heavyComputation, receivePort.sendPort);
print('Main Isolate can do other things while waiting for results...');
// Wait for the computation result from the new Isolate
var result = await receivePort.first;
print('TUTORIAL computation result: sum of 1 to 10000000 = $result');
receivePort.close();
print('Main Isolate ended');
}
Main Isolate startedMain Isolate can do other things while waiting for results...New Isolate starting computation...New Isolate computation complete, result sent TUTORIAL computation result: sum of 1 to 10000000 = 50000005000000Main Isolate ended
### Comparison: Performance Difference With and Without Isolate
## Example
// Execute CPU-intensive task in main Isolate (will block)
int fibonacci(int n){
if(n <=1)return n;
return fibonacci(n -1)+ fibonacci(n -2);
}
void main(){
// Calculate in main Isolateββwill block all other operations
var startTime = DateTime.now();
// Note: If n is too large, this computation will be very time-consuming
// In actual development, such computations should be placed in a separate Isolate
var result = fibonacci(40);
var elapsed = DateTime.now().difference(startTime);
print('TUTORIAL Fibonacci(40) = $result');
print('Time elapsed: ${elapsed.inMilliseconds}ms');
print('(Note: During computation, main Isolate cannot handle other tasks)');
}
> In Flutter applications, if you perform time-consuming synchronous computations in the main Isolate, it will cause UI freezing. The solution is to place the computation task in a separate Isolate, and after computation is complete, pass the result back through messages to update the UI.
* * *
## Message Passing Mechanism
Isolates communicate with each other through SendPort and ReceivePort.
Messages must be serializable (basic types, String, List, Map, etc.), functions or closures cannot be passed.
### Two-way Communication
## Example
import'dart:isolate';
// Work function that runs in the new Isolate
void workerIsolate(SendPort mainSendPort){
// Create its own ReceivePort to receive messages from main Isolate
var workerReceivePort = ReceivePort();
// First send the worker's SendPort to main Isolate to establish a two-way channel
mainSendPort.send(workerReceivePort.sendPort);
print('Worker Isolate: Waiting for tasks...');
// Listen for tasks sent from main Isolate
workerReceivePort.listen((message){
if(message is List){
// Received task: square each number in the list
print('Worker Isolate: Received data $message');
var result = message.map((n)=> n * n).toList();
// Send result back through mainSendPort
mainSendPort.send(result);
}else if(message =='exit'){
print('Worker Isolate: Received exit signal, closing');
workerReceivePort.close();
mainSendPort.send('goodbye');
}
});
}
Future main() async {
print('Main Isolate: Starting');
// Create main receive port
var mainReceivePort = ReceivePort();
// Start Worker Isolate
await Isolate.spawn(workerIsolate, mainReceivePort.sendPort);
// Wait for Worker to send its SendPort (establish two-way communication)
SendPort? workerSendPort;
await for(var msg in mainReceivePort){
if(msg is SendPort){
workerSendPort = msg;
print('Main Isolate: Established two-way communication with Worker');
break;
}
}
// Send tasks through Worker's SendPort
workerSendPort!.send([1,2,3,4,5]);
workerSendPort.send([10,20,30]);
// Receive Worker's computation results
int responseCount =0;
await for(var msg in mainReceivePort){
if(msg is List){
print('Main Isolate: Received result $msg');
responseCount++;
if(responseCount ==2)break;
}
}
// Send exit signal
workerSendPort.send('exit');
await for(var msg in mainReceivePort){
if(msg =='goodbye'){
print('Main Isolate: Worker has exited');
break;
}
}
mainReceivePort.close();
print('TUTORIAL Two-way communication demo ended');
}
Main Isolate: StartingMain Isolate: Established two-way communication with WorkerWorker Isolate: Waiting tasks...Worker Isolate: Received data [1, 2, 3, 4, 5]Main Isolate: Received result [1, 4, 9, 16, 25]Worker Isolate: Received data [10, 20, 30]Main Isolate: Received result [100, 400, 900]Worker Isolate: Received exit signal, closingMain Isolate: Worker has exited TUTORIAL Two-way communication demo ended
### Using Isolate.run for Simplification (Dart 3.0+)
Dart 3.0 introduced Isolate.run(), which greatly simplifies the usage for one-time computation tasks.
## Example
import'dart:isolate';
// A time-consuming computation function
int complexCalculation(int n){
int result =0;
for(int i =0; i < n; i++){
result += i * i;
}
return result;
}
Future main() async {
print('Starting computation...');
// Isolate.run: One line of code, automatically creates Isolate, executes, returns result
var result = await Isolate.run(()=> complexCalculation(10000000));
print('TUTORIAL computation result: $result');
print('Computation complete');
// Comparison: If not using Isolate
print('(If computed directly in main Isolate, it would block other operations)');
}
Starting computation... TUTORIAL computation result: 333333283333335000000Computation complete(If computed directly in main Isolate, it would block other operations)
> Isolate
YouTip