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!

🧠 Test Your Knowledge

What is the main advantage of using isolates in Dart?