Question #30MediumFlutter BasicsImportant

Flutter is single thread or multi thread

#flutter

Answer

Overview

Flutter is single-threaded by default, running on a single main thread (UI thread). However, Flutter supports multi-threading through Isolates for CPU-intensive tasks. This architecture is similar to JavaScript's event loop model.


Flutter Threading Model

text
┌─────────────────────────────────────────┐
│         Main Isolate (UI Thread)        │
│  ┌────────────────────────────────┐     │
│  │      Event Loop                │     │
│  │  ┌──────────────────────┐      │     │
│  │  │  MicroTask Queue     │      │     │
│  │  └──────────────────────┘      │     │
│  │  ┌──────────────────────┐      │     │
│  │  │  Event Queue         │      │     │
│  │  └──────────────────────┘      │     │
│  └────────────────────────────────┘     │
└─────────────────────────────────────────┘
           ↓ Heavy computation
┌─────────────────────────────────────────┐
│      Background Isolates (Optional)     │
│  - Separate memory space                │
│  - No shared state                      │
│  - Message passing only                 │
└─────────────────────────────────────────┘

Single-Threaded Nature

Main Isolate

All Flutter code runs on a single isolate by default:

dart
void main() {
  // Everything runs on main isolate
  runApp(MyApp());
}

On Main Isolate:

  • UI rendering
  • Event handling
  • Gesture detection
  • Widget building
  • State updates

Event Loop

Flutter uses Dart's event loop (similar to JavaScript):

dart
// All these run sequentially on main thread
void example() {
  print('1');
  
  Future.delayed(Duration(seconds: 1), () {
    print('3'); // Added to event queue
  });
  
  print('2');
}

// Output: 1, 2, 3

Why Single-Threaded?

Advantages

BenefitExplanation
No race conditionsNo shared mutable state
Simple debuggingPredictable execution order
Easier to reason aboutNo locks or semaphores
Better UI performanceSmooth 60fps possible

Problem: Blocking Operations

Heavy operations block the UI:

dart
// ❌ Bad - Blocks UI thread
void heavyCalculation() {
  for (int i = 0; i < 1000000000; i++) {
    // UI freezes!
  }
}

// ✅ Good - Use async
Future<void> heavyCalculation() async {
  await compute(_doHeavyWork, data); // Runs in isolate
}

Multi-Threading with Isolates

What are Isolates?

Isolates are independent workers with:

  • Separate memory heap
  • Own event loop
  • No shared state
  • Communication via message passing
dart
┌──────────────┐      Messages      ┌──────────────┐
Main        │ ←─────────────────→ │  BackgroundIsolate(SendPort/Isolate│              │     ReceivePort)    │              │
└──────────────┘                     └──────────────┘

Using Isolates

Method 1: compute() Function (Simple)

For one-time heavy computations.

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

// Heavy function (must be top-level or static)
int calculateSum(int n) {
  int sum = 0;
  for (int i = 0; i <= n; i++) {
    sum += i;
  }
  return sum;
}

// Usage
Future<void> example() async {
  // Runs in background isolate automatically
  final result = await compute(calculateSum, 1000000000);
  print('Sum: $result');
}

compute() Features:

  • ✅ Simple API
  • ✅ Auto-creates and destroys isolate
  • ✅ Perfect for one-off tasks
  • ❌ Creates new isolate each time (overhead)

Method 2: Isolate.spawn() (Advanced)

For long-running background tasks.

dart
import 'dart:isolate';

// Background task
void backgroundTask(SendPort sendPort) {
  // Heavy computation
  int result = 0;
  for (int i = 0; i < 1000000000; i++) {
    result += i;
  }
  
  // Send result back
  sendPort.send(result);
}

// Main isolate
Future<void> runBackgroundTask() async {
  // Create port to receive messages
  final receivePort = ReceivePort();
  
  // Spawn isolate
  await Isolate.spawn(backgroundTask, receivePort.sendPort);
  
  // Wait for result
  final result = await receivePort.first;
  print('Result: $result');
}

Method 3: Bidirectional Communication

dart
import 'dart:isolate';

// Background isolate
void isolateEntry(SendPort mainSendPort) async {
  // Create port to receive messages from main
  final receivePort = ReceivePort();
  
  // Send this isolate's SendPort to main
  mainSendPort.send(receivePort.sendPort);
  
  // Listen for messages
  await for (var message in receivePort) {
    if (message == 'stop') {
      break;
    }
    
    // Process and send back
    final result = message * 2;
    mainSendPort.send(result);
  }
}

// Main isolate
class IsolateManager {
  Isolate? _isolate;
  SendPort? _sendPort;
  
  Future<void> start() async {
    final receivePort = ReceivePort();
    
    // Spawn isolate
    _isolate = await Isolate.spawn(isolateEntry, receivePort.sendPort);
    
    // Get isolate's SendPort
    _sendPort = await receivePort.first as SendPort;
  }
  
  void sendData(int data) {
    _sendPort?.send(data);
  }
  
  void stop() {
    _sendPort?.send('stop');
    _isolate?.kill();
  }
}

// Usage
final manager = IsolateManager();
await manager.start();
manager.sendData(5); // Sends to isolate
manager.stop();

Complete Example: Image Processing

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

// Heavy image processing function
Uint8List processImage(Uint8List imageData) {
  // Apply filters, transformations, etc.
  for (int i = 0; i < imageData.length; i++) {
    imageData[i] = (imageData[i] * 0.5).toInt(); // Darken
  }
  return imageData;
}

class ImageProcessor extends StatefulWidget {
  
  _ImageProcessorState createState() => _ImageProcessorState();
}

class _ImageProcessorState extends State<ImageProcessor> {
  Uint8List? _originalImage;
  Uint8List? _processedImage;
  bool _isProcessing = false;
  
  Future<void> _processInBackground() async {
    if (_originalImage == null) return;
    
    setState(() => _isProcessing = true);
    
    // Process in isolate - UI stays smooth!
    final processed = await compute(processImage, _originalImage!);
    
    setState(() {
      _processedImage = processed;
      _isProcessing = false;
    });
  }
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Image Processor')),
      body: Column(
        children: [
          if (_isProcessing)
            CircularProgressIndicator(),
          
          ElevatedButton(
            onPressed: _processInBackground,
            child: Text('Process Image'),
          ),
          
          if (_processedImage != null)
            Image.memory(_processedImage!),
        ],
      ),
    );
  }
}

When to Use Isolates

✅ Use Isolates For:

  • Image/video processing
  • Large JSON parsing
  • Encryption/decryption
  • Complex calculations
  • File compression
  • Database operations (large datasets)

❌ Don't Use Isolates For:

  • Simple operations
  • UI updates (must be on main thread)
  • Network calls (already async)
  • Small data processing

Platform Threads

Flutter also uses platform threads for native operations:

text
┌────────────────────────────────────────────┐
│  Platform (Android/iOS)                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│  │ UI       │  │ Platform │  │ IO       │ │
│  │ Thread   │  │ Thread   │  │ Thread   │ │
│  └──────────┘  └──────────┘  └──────────┘ │
└────────────────────────────────────────────┘
         ↓              ↓              ↓
┌────────────────────────────────────────────┐
│  Flutter Engine                            │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│  │ UI       │  │ Raster   │  │ IO       │ │
│  │ Thread   │  │ Thread   │  │ Thread   │ │
│  └──────────┘  └──────────┘  └──────────┘ │
└────────────────────────────────────────────┘

Flutter Engine Threads:

  1. UI Thread: Dart code, widget building
  2. Raster Thread: GPU rendering
  3. IO Thread: File I/O, networking
  4. Platform Thread: Native code execution

Comparison Table

FeatureMain IsolateBackground Isolate
PurposeUI and app logicHeavy computation
MemoryShared with widgetsSeparate heap
CommunicationDirectMessage passing
CreationAutomaticManual (spawn/compute)
OverheadNoneIsolate creation cost
Use CaseNormal app codeCPU-intensive tasks

Best Practices

Important: Never block the main isolate with heavy computation

✅ Do

dart
// Good - Use compute for heavy work
final result = await compute(heavyFunction, data);

// Good - Use async for I/O
final response = await http.get(url);

❌ Don't

dart
// Bad - Blocks UI
void calculate() {
  for (int i = 0; i < 1000000000; i++) {
    // UI freezes!
  }
}

Resources