Question #183EasyFlutter Basics

What is concurrency and isolates works?

#flutter

Answer

Overview

Concurrency is running multiple tasks at the same time (or appearing to). Isolates are Dart's mechanism for achieving true parallelism by running code on separate threads.


Concurrency vs Parallelism

Concurrency

Multiple tasks making progress (not necessarily simultaneously).

Example: Single-threaded event loop (async/await).

dart
Future<void> fetchData() async {
  print('Fetching...');
  await Future.delayed(Duration(seconds: 2));
  print('Done');
}

// Tasks are concurrent but run on single thread (event loop)

Parallelism

Multiple tasks running simultaneously on different CPU cores.

Example: Isolates (separate threads).

dart
// Task 1 runs on Core 1
// Task 2 runs on Core 2 (simultaneously)

Dart's Concurrency Model

Single-Threaded Event Loop

Dart is single-threaded by default (like JavaScript).

dart
void main() {
  print('Start');

  Future.delayed(Duration(seconds: 1), () {
    print('Task 1');
  });

  Future.delayed(Duration(seconds: 1), () {
    print('Task 2');
  });

  print('End');
}

// Output:
// Start
// End
// Task 1
// Task 2

Limitation: Heavy computation blocks the UI thread.

dart
// ❌ Blocks UI for 5 seconds
void main() {
  heavyComputation(); // Freezes UI
  print('Done');
}

void heavyComputation() {
  int sum = 0;
  for (int i = 0; i < 10000000000; i++) {
    sum += i;
  }
}

Isolates: True Parallelism

Isolates are separate threads with their own memory.

Key Concepts

  • Each isolate has its own memory (no shared state)
  • Isolates communicate via message passing (ports)
  • Isolates run on separate CPU cores

Creating Isolates

Method 1: compute() Function (Simplest)

dart
import 'package:flutter/foundation.dart';

// Heavy computation function
int heavyComputation(int n) {
  int sum = 0;
  for (int i = 0; i < n; i++) {
    sum += i;
  }
  return sum;
}

void main() async {
  print('Start');

  // Run in isolate (separate thread)
  final result = await compute(heavyComputation, 1000000000);

  print('Result: $result');
  print('Done');
}

Benefits:

  • ✅ UI remains responsive
  • ✅ Simple API
  • ✅ Automatic message passing

Method 2: Isolate.spawn() (Manual)

dart
import 'dart:isolate';

// Entry point for isolate
void heavyComputationIsolate(SendPort sendPort) {
  int sum = 0;
  for (int i = 0; i < 1000000000; i++) {
    sum += i;
  }

  // Send result back to main isolate
  sendPort.send(sum);
}

void main() async {
  print('Start');

  // Create receive port
  final receivePort = ReceivePort();

  // Spawn isolate
  await Isolate.spawn(heavyComputationIsolate, receivePort.sendPort);

  // Wait for result
  final result = await receivePort.first;

  print('Result: $result');
  print('Done');
}

Method 3: Bidirectional Communication

dart
import 'dart:isolate';

// Isolate entry point
void workerIsolate(SendPort mainSendPort) async {
  // Create receive port for isolate
  final workerReceivePort = ReceivePort();

  // Send isolate's send port to main
  mainSendPort.send(workerReceivePort.sendPort);

  // Listen for messages from main
  await for (var message in workerReceivePort) {
    if (message == 'stop') {
      break;
    }

    // Process message
    final result = message * 2;

    // Send result back
    mainSendPort.send(result);
  }
}

void main() async {
  // Create receive port for main
  final mainReceivePort = ReceivePort();

  // Spawn isolate
  await Isolate.spawn(workerIsolate, mainReceivePort.sendPort);

  // Get isolate's send port
  final workerSendPort = await mainReceivePort.first as SendPort;

  // Send messages to isolate
  workerSendPort.send(5);
  workerSendPort.send(10);
  workerSendPort.send(15);

  // Receive results
  mainReceivePort.listen((result) {
    print('Result: $result');
  });

  // Stop isolate
  await Future.delayed(Duration(seconds: 2));
  workerSendPort.send('stop');
}

Flutter Example: Image Processing

dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'dart:ui' as ui;

// Heavy image processing function
ui.Image processImage(ui.Image image) {
  // Apply filters, transformations, etc.
  // (Simplified example)
  return image;
}

class ImageProcessorScreen extends StatefulWidget {
  
  _ImageProcessorScreenState createState() => _ImageProcessorScreenState();
}

class _ImageProcessorScreenState extends State<ImageProcessorScreen> {
  ui.Image? _processedImage;
  bool _isProcessing = false;

  Future<void> _processImage(ui.Image image) async {
    setState(() {
      _isProcessing = true;
    });

    // Run in isolate (doesn't block UI)
    final result = await compute(processImage, image);

    setState(() {
      _processedImage = result;
      _isProcessing = false;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Image Processor')),
      body: Center(
        child: _isProcessing
            ? CircularProgressIndicator()
            : _processedImage != null
                ? RawImage(image: _processedImage)
                : Text('No image'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Load and process image
          loadImage().then(_processImage);
        },
        child: Icon(Icons.image),
      ),
    );
  }

  Future<ui.Image> loadImage() async {
    // Load image from assets/network
    // (Simplified)
    throw UnimplementedError();
  }
}

Common Use Cases

1. JSON Parsing

dart
import 'package:flutter/foundation.dart';
import 'dart:convert';

List<User> parseUsers(String jsonString) {
  final List<dynamic> jsonList = jsonDecode(jsonString);
  return jsonList.map((json) => User.fromJson(json)).toList();
}

// Usage
final users = await compute(parseUsers, jsonString);

2. Database Operations

dart
Future<List<User>> queryDatabase() async {
  return await compute(_queryDatabaseIsolate, null);
}

List<User> _queryDatabaseIsolate(_) {
  // Perform heavy database query
  return database.query('SELECT * FROM users');
}

3. Encryption/Decryption

dart
String encryptData(String data) {
  // Heavy encryption
  return encrypted;
}

final encrypted = await compute(encryptData, sensitiveData);

Isolate Limitations

No Shared Memory

dart
// ❌ Cannot access shared variables
int counter = 0;

void isolateFunction(SendPort sendPort) {
  counter++; // ❌ Error: counter is not accessible
}

Only Primitive Types

Isolates can only pass simple types (int, String, List, Map).

dart
// ✅ Works
compute(myFunction, 42);
compute(myFunction, 'hello');
compute(myFunction, [1, 2, 3]);

// ❌ Doesn't work (complex objects)
compute(myFunction, MyCustomClass());

Solution: Serialize to JSON.

dart
// Serialize
final jsonString = jsonEncode(user.toJson());
final result = await compute(processUser, jsonString);

// Deserialize in isolate
User processUser(String jsonString) {
  final user = User.fromJson(jsonDecode(jsonString));
  // Process user
  return user;
}

Performance Comparison

Test: Calculate sum of 1 billion numbers

MethodTimeUI Blocked?
Main thread5000ms✅ Yes (freezes UI)
async/await5000ms✅ Yes (still on main thread)
Isolate (compute)5000ms❌ No (UI responsive)

Best Practices

dart
// ✅ Use compute() for simple tasks
final result = await compute(heavyFunction, data);

// ✅ Use Isolate.spawn() for long-running tasks
await Isolate.spawn(workerIsolate, sendPort);

// ✅ Use isolates for heavy computation
// - JSON parsing (large files)
// - Image processing
// - Encryption/decryption
// - Database queries

// ❌ Don't use isolates for simple tasks (overhead)
// await compute(() => 1 + 1); ❌ Overkill

// ❌ Don't access UI from isolates
// Isolates cannot call setState() or access widgets

Isolate Pool (Advanced)

For multiple concurrent tasks.

dart
import 'dart:isolate';

class IsolatePool {
  final int size;
  final List<SendPort> _workers = [];

  IsolatePool({this.size = 4});

  Future<void> init() async {
    for (int i = 0; i < size; i++) {
      final receivePort = ReceivePort();
      await Isolate.spawn(_workerIsolate, receivePort.sendPort);
      final sendPort = await receivePort.first as SendPort;
      _workers.add(sendPort);
    }
  }

  static void _workerIsolate(SendPort mainSendPort) {
    final receivePort = ReceivePort();
    mainSendPort.send(receivePort.sendPort);

    receivePort.listen((message) {
      // Process message
      final result = message * 2;
      mainSendPort.send(result);
    });
  }

  void execute(int data) {
    final worker = _workers[data % size];
    worker.send(data);
  }
}

Summary

ConceptDescription
ConcurrencyMultiple tasks making progress (single thread)
ParallelismMultiple tasks running simultaneously (multiple threads)
IsolateSeparate thread with own memory
compute()Simple API for running functions in isolates
Message PassingIsolates communicate via SendPort/ReceivePort

When to use isolates:

  • ✅ Heavy computation (> 100ms)
  • ✅ JSON parsing (large files)
  • ✅ Image processing
  • ✅ Encryption/database operations
  • ❌ Simple async operations (use async/await)

Learn more: