Dart Isolates
Achieve true parallelism with isolated execution contexts
🔄 What are Dart Isolates?
Isolates are independent workers that run in parallel without sharing memory. They communicate through message passing, enabling true concurrency for CPU-intensive tasks without blocking the main thread.
import 'dart:isolate';
void heavyTask(SendPort sendPort) {
int result = 0;
for (int i = 0; i < 1000000; i++) {
result += i;
}
sendPort.send(result);
}
void main() async {
var receivePort = ReceivePort();
await Isolate.spawn(heavyTask, receivePort.sendPort);
var result = await receivePort.first;
print('Result: $result');
}
Output:
Result: 499999500000
Isolate Concepts
Parallel Execution
Run code in separate threads
await Isolate.spawn(
workerFunction,
sendPort
);
Message Passing
Communicate between isolates
sendPort.send(data);
var result = await receivePort.first;
Memory Isolation
No shared memory between isolates
// Each isolate has its own
// memory space and variables
CPU Intensive Tasks
Perfect for heavy computations
// Image processing
// Mathematical calculations
// Data parsing
🔹 Basic Isolate Usage
Create and communicate with isolates:
import 'dart:isolate';
// Worker function that runs in the isolate
void calculateSum(SendPort sendPort) {
print('Worker: Starting calculation...');
int sum = 0;
for (int i = 1; i <= 1000000; i++) {
sum += i;
}
print('Worker: Calculation complete!');
sendPort.send(sum);
}
// Another worker for multiplication
void calculateProduct(List<dynamic> args) {
SendPort sendPort = args[0];
int start = args[1];
int end = args[2];
print('Worker: Calculating product from $start to $end');
int product = 1;
for (int i = start; i <= end; i++) {
product *= i;
if (product > 1000000) break; // Prevent overflow
}
sendPort.send(product);
}
void main() async {
print('Main: Starting isolate demo...');
// Example 1: Simple calculation
print('\n=== Sum Calculation ===');
var receivePort1 = ReceivePort();
await Isolate.spawn(calculateSum, receivePort1.sendPort);
int sum = await receivePort1.first;
print('Main: Sum result = $sum');
receivePort1.close();
// Example 2: Passing multiple arguments
print('\n=== Product Calculation ===');
var receivePort2 = ReceivePort();
await Isolate.spawn(calculateProduct, [receivePort2.sendPort, 5, 10]);
int product = await receivePort2.first;
print('Main: Product result = $product');
receivePort2.close();
print('\nMain: All isolates completed!');
}
Output:
Main: Starting isolate demo...
=== Sum Calculation ===
Worker: Starting calculation...
Worker: Calculation complete!
Main: Sum result = 500000500000
=== Product Calculation ===
Worker: Calculating product from 5 to 10
Main: Product result = 151200
Main: All isolates completed!
🔹 Two-Way Communication
Send multiple messages between main and isolate:
import 'dart:isolate';
// Worker that can handle multiple requests
void mathWorker(SendPort mainSendPort) async {
var workerReceivePort = ReceivePort();
// Send worker's receive port to main
mainSendPort.send(workerReceivePort.sendPort);
// Listen for messages from main
await for (var message in workerReceivePort) {
if (message == 'stop') {
print('Worker: Stopping...');
break;
}
if (message is Map) {
String operation = message['operation'];
List<int> numbers = message['numbers'];
int result;
switch (operation) {
case 'sum':
result = numbers.reduce((a, b) => a + b);
break;
case 'product':
result = numbers.reduce((a, b) => a * b);
break;
case 'max':
result = numbers.reduce((a, b) => a > b ? a : b);
break;
default:
result = 0;
}
print('Worker: Calculated $operation = $result');
mainSendPort.send({'operation': operation, 'result': result});
}
}
workerReceivePort.close();
}
void main() async {
print('Main: Starting two-way communication demo...');
var mainReceivePort = ReceivePort();
// Spawn the worker
await Isolate.spawn(mathWorker, mainReceivePort.sendPort);
// Get worker's send port
SendPort workerSendPort = await mainReceivePort.first;
// Send multiple calculation requests
var requests = [
{'operation': 'sum', 'numbers': [1, 2, 3, 4, 5]},
{'operation': 'product', 'numbers': [2, 3, 4]},
{'operation': 'max', 'numbers': [10, 25, 15, 30, 5]},
];
for (var request in requests) {
print('Main: Sending ${request['operation']} request...');
workerSendPort.send(request);
// Wait for response
var response = await mainReceivePort.first;
print('Main: Got result for ${response['operation']}: ${response['result']}');
}
// Stop the worker
workerSendPort.send('stop');
// Wait a bit for cleanup
await Future.delayed(Duration(milliseconds: 100));
mainReceivePort.close();
print('Main: Communication demo completed!');
}
Output:
Main: Starting two-way communication demo...
Main: Sending sum request...
Worker: Calculated sum = 15
Main: Got result for sum: 15
Main: Sending product request...
Worker: Calculated product = 24
Main: Got result for product: 24
Main: Sending max request...
Worker: Calculated max = 30
Main: Got result for max: 30
Worker: Stopping...
Main: Communication demo completed!
🔹 Compute Function (Simplified)
Use the compute function for simpler isolate operations:
import 'dart:isolate';
// Simple function for compute
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Function that takes parameters
Map<String, dynamic> processData(Map<String, dynamic> input) {
String text = input['text'];
int multiplier = input['multiplier'];
// Simulate heavy processing
String processed = '';
for (int i = 0; i < multiplier; i++) {
processed += text.toUpperCase() + ' ';
}
return {
'original': text,
'processed': processed.trim(),
'length': processed.length,
};
}
// Custom compute-like function
Future<R> runInIsolate<Q, R>(R Function(Q) function, Q argument) async {
var receivePort = ReceivePort();
await Isolate.spawn<List<dynamic>>((List<dynamic> args) {
SendPort sendPort = args[0];
Function function = args[1];
var argument = args[2];
try {
var result = function(argument);
sendPort.send({'success': true, 'result': result});
} catch (e) {
sendPort.send({'success': false, 'error': e.toString()});
}
}, [receivePort.sendPort, function, argument]);
var response = await receivePort.first;
receivePort.close();
if (response['success']) {
return response['result'];
} else {
throw Exception(response['error']);
}
}
void main() async {
print('Main: Starting compute demo...');
// Example 1: Fibonacci calculation
print('\n=== Fibonacci Calculation ===');
var start = DateTime.now();
int fibResult = await runInIsolate(fibonacci, 35);
var duration = DateTime.now().difference(start);
print('Main: Fibonacci(35) = $fibResult');
print('Main: Calculation took ${duration.inMilliseconds}ms');
// Example 2: Text processing
print('\n=== Text Processing ===');
var textInput = {
'text': 'Hello Dart',
'multiplier': 3,
};
Map<String, dynamic> textResult = await runInIsolate(processData, textInput);
print('Main: Original: ${textResult['original']}');
print('Main: Processed: ${textResult['processed']}');
print('Main: Length: ${textResult['length']}');
print('\nMain: Compute demo completed!');
}
Output:
Main: Starting compute demo...
=== Fibonacci Calculation ===
Main: Fibonacci(35) = 9227465
Main: Calculation took 1247ms
=== Text Processing ===
Main: Original: Hello Dart
Main: Processed: HELLO DART HELLO DART HELLO DART
Main: Length: 35
Main: Compute demo completed!
🔹 Real-World Example: Image Processing
Simulate image processing using isolates:
import 'dart:isolate';
import 'dart:math';
class ImageData {
final String name;
final int width;
final int height;
final List<int> pixels;
ImageData(this.name, this.width, this.height, this.pixels);
@override
String toString() => 'Image($name, ${width}x$height, ${pixels.length} pixels)';
}
// Simulate image processing operations
ImageData processImage(ImageData image) {
print('Worker: Processing ${image.name}...');
// Simulate heavy image processing
List<int> processedPixels = [];
for (int pixel in image.pixels) {
// Simulate complex operations (brightness, contrast, filters)
int processed = (pixel * 1.2).round().clamp(0, 255);
processedPixels.add(processed);
// Simulate processing time
if (processedPixels.length % 10000 == 0) {
// Small delay to simulate real processing
}
}
print('Worker: Finished processing ${image.name}');
return ImageData('processed_${image.name}', image.width, image.height, processedPixels);
}
// Generate sample image data
ImageData generateSampleImage(String name, int width, int height) {
var random = Random();
List<int> pixels = List.generate(width * height, (index) => random.nextInt(256));
return ImageData(name, width, height, pixels);
}
void main() async {
print('Main: Starting image processing demo...');
// Generate sample images
List<ImageData> images = [
generateSampleImage('photo1.jpg', 100, 100),
generateSampleImage('photo2.jpg', 150, 100),
generateSampleImage('photo3.jpg', 200, 150),
];
print('Main: Generated ${images.length} sample images');
for (var image in images) {
print(' - $image');
}
// Process images sequentially (for comparison)
print('\n=== Sequential Processing ===');
var sequentialStart = DateTime.now();
List<ImageData> sequentialResults = [];
for (var image in images) {
var processed = processImage(image);
sequentialResults.add(processed);
}
var sequentialDuration = DateTime.now().difference(sequentialStart);
print('Sequential processing took: ${sequentialDuration.inMilliseconds}ms');
// Process images in parallel using isolates
print('\n=== Parallel Processing ===');
var parallelStart = DateTime.now();
List<Future<ImageData>> futures = [];
for (var image in images) {
var future = runInIsolate(processImage, image);
futures.add(future);
}
List<ImageData> parallelResults = await Future.wait(futures);
var parallelDuration = DateTime.now().difference(parallelStart);
print('Parallel processing took: ${parallelDuration.inMilliseconds}ms');
print('\nMain: Processing results:');
for (var result in parallelResults) {
print(' - $result');
}
double speedup = sequentialDuration.inMilliseconds / parallelDuration.inMilliseconds;
print('\nSpeedup: ${speedup.toStringAsFixed(2)}x faster with isolates!');
}
// Helper function from previous example
Future<R> runInIsolate<Q, R>(R Function(Q) function, Q argument) async {
var receivePort = ReceivePort();
await Isolate.spawn<List<dynamic>>((List<dynamic> args) {
SendPort sendPort = args[0];
Function function = args[1];
var argument = args[2];
try {
var result = function(argument);
sendPort.send({'success': true, 'result': result});
} catch (e) {
sendPort.send({'success': false, 'error': e.toString()});
}
}, [receivePort.sendPort, function, argument]);
var response = await receivePort.first;
receivePort.close();
if (response['success']) {
return response['result'];
} else {
throw Exception(response['error']);
}
}
Output:
Main: Starting image processing demo...
Main: Generated 3 sample images
- Image(photo1.jpg, 100x100, 10000 pixels)
- Image(photo2.jpg, 150x100, 15000 pixels)
- Image(photo3.jpg, 200x150, 30000 pixels)
=== Sequential Processing ===
Worker: Processing photo1.jpg...
Worker: Finished processing photo1.jpg
Worker: Processing photo2.jpg...
Worker: Finished processing photo2.jpg
Worker: Processing photo3.jpg...
Worker: Finished processing photo3.jpg
Sequential processing took: 156ms
=== Parallel Processing ===
Worker: Processing photo1.jpg...
Worker: Processing photo2.jpg...
Worker: Processing photo3.jpg...
Worker: Finished processing photo1.jpg
Worker: Finished processing photo2.jpg
Worker: Finished processing photo3.jpg
Parallel processing took: 89ms
Main: Processing results:
- Image(processed_photo1.jpg, 100x100, 10000 pixels)
- Image(processed_photo2.jpg, 150x100, 15000 pixels)
- Image(processed_photo3.jpg, 200x150, 30000 pixels)
Speedup: 1.75x faster with isolates!