Lesson 18 / 25

Isolates and the Event Loop in Depth

Explain the event and microtask queues and move heavy work to isolates.

Parallelism without shared memory

Each isolate has its own memory heap and a single thread running an event loop. The loop first drains the microtask queue (scheduled by scheduleMicrotask and completing futures) and then processes the next item from the event queue (timers, I/O, user input). Asynchronous code therefore keeps a single isolate responsive for I/O-bound work, but CPU-heavy work (parsing a huge JSON file, image processing, encryption, complex calculations) running on the main isolate blocks the event loop and freezes a Flutter UI. The fix is to run such work in another isolate. Isolates do not share memory: they communicate by message passing through ports, so there are no data races on shared objects. Isolate.run(() => heavyWork(data)) (Dart 2.19+) is the simplest API: it spawns an isolate, runs the function, returns the result and shuts down. Flutter's compute() does the same. For long-lived background workers, use Isolate.spawn with SendPort and ReceivePort. On the web, isolates map to web workers with some limitations.

Moving heavy parsing off the main isolate

Isolate.run keeps the UI isolate free while CPU work runs elsewhere.

import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

class Order {
  Order(this.id, this.totalPaise);
  final String id;
  final int totalPaise;

  factory Order.fromJson(Map<String, dynamic> j) => Order(j['id'] as String, j['total_paise'] as int);
}

List<Order> parseOrders(String body) {
  final list = jsonDecode(body) as List<dynamic>;
  return [for (final item in list) Order.fromJson(item as Map<String, dynamic>)];
}

Future<int> totalRevenue(String hugeJsonBody) async {
  final orders = await Isolate.run(() => parseOrders(hugeJsonBody));   // runs in a new isolate
  return orders.fold<int>(0, (sum, o) => sum + o.totalPaise);
}

// event loop ordering example
void main() {
  print('1 sync');
  Future(() => print('4 event queue'));
  scheduleMicrotask(() => print('3 microtask'));
  print('2 sync');
}

async does not mean parallel

Marking a CPU-heavy function async does not move it off the main isolate; it still blocks the event loop while it computes. Use Isolate.run for genuine parallel work.

Quick check: How do Dart isolates communicate?

  • By passing messages, since each isolate has its own memory
  • By sharing objects in memory with locks
  • Through global variables
  • They cannot communicate
Answer

By passing messages, since each isolate has its own memory — Isolates have separate heaps and exchange data via messages.