Dart Isolates en Concurrency in 2026: compute, Async en Sollicitatievragen

Beheers Dart isolates, de compute-functie en async-patronen in Flutter. Concurrency-modellen, prestatie-optimalisatie en technische sollicitatievragen.

Dart Isolates en Concurrency in 2026: compute, Async en Sollicitatievragen

Dart isolates bieden echte parallelle uitvoering in Flutter-applicaties en lossen de single-threaded beperking op die UI-haperingen veroorzaakt tijdens zware berekeningen. In tegenstelling tot threads in andere talen delen isolates geen geheugen en communiceren ze uitsluitend via message passing, waardoor concurrent code veiliger en begrijpelijker wordt.

Belangrijke Update voor 2026

Dart 3.7 introduceerde lichtgewicht isolates met 50% minder geheugenoverhead. De Isolate.run() API dekt nu de meeste use cases waarvoor eerder handmatige isolate-spawning nodig was, terwijl compute() de standaardkeuze blijft voor Flutter-specifieke workloads.

Het Dart Concurrency Model Begrijpen

Dart voert code uit in een single-threaded event loop binnen elke isolate. Dit betekent dat async/await geen parallelisme biedt - het maakt alleen non-blocking I/O-operaties mogelijk. Wanneer een CPU-intensieve taak op de hoofd-isolate draait, bevriest de UI omdat de event loop geen frame-callbacks kan verwerken.

De oplossing is om zwaar werk naar aparte isolates te verplaatsen. Elke isolate heeft zijn eigen geheugenheap en event loop, draaiend op een aparte thread beheerd door de Dart VM. Communicatie gebeurt via SendPort en ReceivePort, die berichten serialiseren tussen isolates.

main.dartdart
import 'dart:isolate';

// Heavy computation that would block the UI if run on main isolate
int computeFibonacci(int n) {
  if (n <= 1) return n;
  return computeFibonacci(n - 1) + computeFibonacci(n - 2);
}

Future<void> main() async {
  // Isolate.run() handles spawning, message passing, and cleanup automatically
  final result = await Isolate.run(() => computeFibonacci(40));
  print('Fibonacci(40) = $result'); // Computed without blocking main thread
}

De Isolate.run() functie geintroduceerd in Dart 2.19 vereenvoudigt het gangbare patroon van het uitvoeren van een enkele berekening in een achtergrond-isolate. Het handelt automatisch port-creatie, berichtserialisatie en isolate-opruiming af.

Gebruik van compute() in Flutter-Applicaties

Flutter biedt de compute() functie specifiek ontworpen voor het verplaatsen van werk van de UI-isolate. In tegenstelling tot de ruwe isolate API's integreert compute() met het Flutter-framework en handelt het de boilerplate van isolate-management af.

image_processor.dartdart
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as img;

// Top-level function required for compute() - cannot be a closure or method
Uint8List _applyGrayscaleFilter(Uint8List imageBytes) {
  final image = img.decodeImage(imageBytes)!;
  final grayscale = img.grayscale(image);
  return Uint8List.fromList(img.encodePng(grayscale));
}

class ImageProcessor {
  Future<Uint8List> processImage(Uint8List rawImage) async {
    // compute() spawns isolate, runs function, returns result, disposes isolate
    return await compute(_applyGrayscaleFilter, rawImage);
  }
}

De functie die aan compute() wordt doorgegeven moet een top-level functie of een statische methode zijn. Instance methods en closures vangen state op die niet geserialiseerd kan worden over isolate-grenzen.

Veelgemaakte Fout

Het doorgeven van niet-serialiseerbare objecten aan isolates veroorzaakt runtime-fouten. Classes moeten eenvoudige data-objecten zijn - geen BuildContext, geen Stream referenties, geen singletons. Extraheer alleen de primitieve data die nodig is voor de berekening.

Bidirectionele Communicatie met Ports

Complexe scenario's vereisen doorlopende communicatie tussen isolates in plaats van een enkele request-response cyclus. Langlopende achtergrondtaken, streaming dataverwerking en worker pools hebben allemaal bidirectionele message passing nodig.

background_worker.dartdart
import 'dart:isolate';
import 'dart:async';

class BackgroundWorker {
  late Isolate _isolate;
  late SendPort _sendPort;
  final _responseController = StreamController<dynamic>.broadcast();
  
  Stream<dynamic> get responses => _responseController.stream;

  Future<void> start() async {
    final receivePort = ReceivePort();
    
    // Spawn isolate with initial SendPort for bidirectional setup
    _isolate = await Isolate.spawn(_workerEntryPoint, receivePort.sendPort);
    
    // First message from worker contains its SendPort
    final completer = Completer<SendPort>();
    receivePort.listen((message) {
      if (message is SendPort) {
        completer.complete(message);
      } else {
        _responseController.add(message); // Forward results to stream
      }
    });
    
    _sendPort = await completer.future;
  }

  void sendTask(Map<String, dynamic> task) {
    _sendPort.send(task);
  }

  void dispose() {
    _isolate.kill(priority: Isolate.immediate);
    _responseController.close();
  }
}

// Entry point runs in the background isolate
void _workerEntryPoint(SendPort mainSendPort) {
  final workerReceivePort = ReceivePort();
  mainSendPort.send(workerReceivePort.sendPort); // Send port back to main
  
  workerReceivePort.listen((message) {
    final task = message as Map<String, dynamic>;
    final result = _processTask(task); // Heavy computation here
    mainSendPort.send(result);
  });
}

dynamic _processTask(Map<String, dynamic> task) {
  // Simulate CPU-intensive work
  return {'taskId': task['id'], 'processed': true};
}

Dit patroon vestigt een persistente worker-isolate die meerdere taken accepteert in de loop van de tijd. De hoofd-isolate stuurt taken via SendPort, en de worker antwoordt via zijn eigen port. De state binnen de worker-isolate blijft behouden tussen taken, wat caching en incrementele verwerking mogelijk maakt.

Async/Await vs Isolates: Het Juiste Gereedschap Kiezen

Een veelgestelde sollicitatievraag voor Flutter-ontwikkelaars betreft wanneer async/await te gebruiken versus isolates. Het onderscheid is belangrijk voor applicatieprestaties.

| Scenario | Gebruik async/await | Gebruik isolates | |----------|--------------------|-----------------| | Netwerkverzoeken | Ja | Nee | | Bestand-I/O | Ja | Nee | | JSON parsing (klein) | Ja | Nee | | JSON parsing (>1MB) | Nee | Ja | | Beeldverwerking | Nee | Ja | | Cryptografische operaties | Nee | Ja | | Database queries | Ja | Soms | | Complexe berekeningen | Nee | Ja |

Async/await behandelt I/O-bound operaties efficient omdat de Dart VM het wachten delegeert aan het besturingssysteem. De event loop blijft vrij om UI-events te verwerken. Isolates lossen CPU-bound problemen op waarbij de berekening zelf tijd kost ongeacht I/O.

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

class JsonService {
  // Small payloads: async is sufficient, isolate overhead not justified
  Future<Map<String, dynamic>> parseSmallJson(String json) async {
    return jsonDecode(json) as Map<String, dynamic>;
  }

  // Large payloads: isolate prevents UI jank during parsing
  Future<List<dynamic>> parseLargeJson(String json) async {
    return await compute(_parseJsonList, json);
  }
}

List<dynamic> _parseJsonList(String json) {
  return jsonDecode(json) as List<dynamic>;
}

Klaar om je Flutter gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Foutafhandeling Over Isolate-Grenzen

Fouten die in isolates worden gegooid propageren niet automatisch naar de aanroepende isolate. Zonder correcte afhandeling verdwijnen exceptions stilletjes, wat moeilijk te debuggen problemen veroorzaakt in productie-apps.

safe_isolate.dartdart
import 'dart:isolate';

class IsolateResult<T> {
  final T? value;
  final Object? error;
  final StackTrace? stackTrace;
  
  IsolateResult.success(this.value) : error = null, stackTrace = null;
  IsolateResult.failure(this.error, this.stackTrace) : value = null;
  
  bool get isSuccess => error == null;
}

Future<IsolateResult<T>> runSafeIsolate<T>(T Function() computation) async {
  try {
    final result = await Isolate.run(() {
      try {
        return IsolateResult<T>.success(computation());
      } catch (e, stack) {
        return IsolateResult<T>.failure(e, stack);
      }
    });
    return result;
  } catch (e, stack) {
    // Handle isolate spawn failures
    return IsolateResult<T>.failure(e, stack);
  }
}

// Usage
Future<void> processData() async {
  final result = await runSafeIsolate(() => riskyComputation());
  
  if (result.isSuccess) {
    print('Result: ${result.value}');
  } else {
    print('Error: ${result.error}');
    print('Stack: ${result.stackTrace}');
  }
}

int riskyComputation() {
  throw Exception('Something went wrong');
}

Het wrapper-patroon vangt exceptions binnen de isolate op en retourneert ze als data, waardoor fouten naar boven komen in de aanroepende code. Deze aanpak weerspiegelt het Result-patroon uit Rust en sluit aan bij functionele foutafhandelingspraktijken.

Prestatie-Optimalisatie met Isolate Pools

Het spawnen van isolates brengt overhead met zich mee - geheugenallocatie, JIT-compilatie van code in de nieuwe isolate en berichtserialisatie. Voor applicaties die veel onafhankelijke taken verwerken, verbetert het hergebruiken van isolates via een pool de doorvoer.

isolate_pool.dartdart
import 'dart:async';
import 'dart:collection';
import 'dart:isolate';

class IsolatePool {
  final int size;
  final Queue<_PooledIsolate> _available = Queue();
  final List<_PooledIsolate> _all = [];
  bool _initialized = false;

  IsolatePool({this.size = 4});

  Future<void> initialize() async {
    if (_initialized) return;
    
    for (int i = 0; i < size; i++) {
      final pooled = await _PooledIsolate.spawn();
      _all.add(pooled);
      _available.add(pooled);
    }
    _initialized = true;
  }

  Future<R> execute<R>(R Function() task) async {
    if (!_initialized) await initialize();
    
    // Wait for available isolate
    while (_available.isEmpty) {
      await Future.delayed(const Duration(milliseconds: 10));
    }
    
    final isolate = _available.removeFirst();
    try {
      return await isolate.run(task);
    } finally {
      _available.add(isolate); // Return to pool
    }
  }

  Future<void> dispose() async {
    for (final isolate in _all) {
      isolate.dispose();
    }
    _all.clear();
    _available.clear();
  }
}

class _PooledIsolate {
  final Isolate isolate;
  final SendPort sendPort;
  final ReceivePort receivePort;

  _PooledIsolate._(this.isolate, this.sendPort, this.receivePort);

  static Future<_PooledIsolate> spawn() async {
    final receivePort = ReceivePort();
    final isolate = await Isolate.spawn(_poolWorker, receivePort.sendPort);
    final sendPort = await receivePort.first as SendPort;
    return _PooledIsolate._(isolate, sendPort, receivePort);
  }

  Future<R> run<R>(R Function() task) async {
    final responsePort = ReceivePort();
    sendPort.send([task, responsePort.sendPort]);
    return await responsePort.first as R;
  }

  void dispose() {
    isolate.kill();
    receivePort.close();
  }
}

void _poolWorker(SendPort mainPort) {
  final workerPort = ReceivePort();
  mainPort.send(workerPort.sendPort);
  
  workerPort.listen((message) {
    final task = message[0] as Function;
    final replyPort = message[1] as SendPort;
    replyPort.send(task());
  });
}

De pool onderhoudt een vast aantal opgewarmde isolates die klaar staan om werk te accepteren. Taken worden in een wachtrij geplaatst wanneer alle isolates bezet zijn, en voltooide isolates keren direct terug naar de beschikbare pool.

Veelgestelde Sollicitatievragen over Dart Concurrency

Technische sollicitatiegesprekken testen vaak het begrip van Darts concurrency-model. Voorbereiding op deze vragen demonstreert diepgaande kennis voorbij basis Flutter widget-kennis. De Flutter sollicitatievragen module behandelt aanvullende widget- en state management-onderwerpen.

V: Wat gebeurt er als setState() wordt aangeroepen vanuit een isolate?

Het aanroepen van setState() vanuit een achtergrond-isolate faalt omdat BuildContext en widget-state alleen bestaan in het geheugen van de hoofd-isolate. De isolate kan niet direct objecten van de hoofd-isolate benaderen. In plaats daarvan moet het berekende resultaat teruggestuurd worden naar de hoofd-isolate en daar setState() worden aangeroepen:

counter_page.dartdart
import 'dart:isolate';
import 'package:flutter/material.dart';

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});
  
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int _result = 0;
  bool _computing = false;

  Future<void> _computeInBackground() async {
    setState(() => _computing = true);
    
    // Computation runs in isolate
    final result = await Isolate.run(() => _heavyCalculation(1000000));
    
    // setState called on main isolate after receiving result
    setState(() {
      _result = result;
      _computing = false;
    });
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Result: $_result'),
        ElevatedButton(
          onPressed: _computing ? null : _computeInBackground,
          child: _computing 
              ? const CircularProgressIndicator() 
              : const Text('Compute'),
        ),
      ],
    );
  }
}

int _heavyCalculation(int iterations) {
  int sum = 0;
  for (int i = 0; i < iterations; i++) {
    sum += i;
  }
  return sum;
}

V: Kunnen isolates objecten delen?

Isolates kunnen geen muteerbare objecten delen. Elke isolate heeft zijn eigen heap, en objecten die tussen isolates worden doorgegeven worden geserialiseerd en gekopieerd. Immutable objecten en primitieven kunnen efficient worden overgedragen omdat Dart hun overdracht optimaliseert. Muteerbare state vereist expliciete synchronisatie via message passing.

V: Hoe werkt async/await met isolates?

Async/await binnen een isolate werkt identiek aan de hoofd-isolate - elke isolate heeft zijn eigen event loop. Het awaiten van een Future in een isolate blokkeert niet de event loop van de hoofd-isolate. De Isolate.run() functie zelf retourneert een Future die voltooit wanneer de isolate klaar is, en integreert naadloos met async code op de hoofd-isolate.

Debuggen van Isolate-Gebaseerde Code

Het debuggen van multi-isolate applicaties vereist begrip dat breakpoints en logging zich anders gedragen over isolate-grenzen.

debug_utils.dartdart
import 'dart:developer' as developer;
import 'dart:isolate';

void debugPrint(String message) {
  // developer.log works across isolates and appears in DevTools
  developer.log(
    message,
    name: 'IsolateDebug',
    time: DateTime.now(),
  );
}

Future<void> debuggableComputation() async {
  await Isolate.run(() {
    debugPrint('Starting computation in isolate');
    
    // Timeline events visible in DevTools performance tab
    developer.Timeline.startSync('HeavyWork');
    final result = performHeavyWork();
    developer.Timeline.finishSync();
    
    debugPrint('Computation complete: $result');
    return result;
  });
}

int performHeavyWork() {
  int sum = 0;
  for (int i = 0; i < 10000000; i++) {
    sum += i;
  }
  return sum;
}

De prestatie-weergave van Flutter DevTools toont timeline-events van alle isolates, waardoor parallelle uitvoering gevisualiseerd kan worden en knelpunten geidentificeerd kunnen worden.

DevTools Tip

De CPU Profiler in DevTools toont welke isolates de meeste verwerkingstijd verbruiken. De flame chart scheidt uitvoering per isolate, waardoor zichtbaar wordt of werk correct verdeeld is.

Conclusie

  • Isolates bieden echte paralleliteit in Dart, draaiend op aparte threads met geisoleerde geheugenheaps
  • Gebruik Isolate.run() voor eenvoudige eenmalige berekeningen, compute() voor Flutter-specifieke workloads
  • Async/await behandelt I/O-bound werk; isolates lossen CPU-bound problemen op die de event loop blokkeren
  • Functies die aan isolates worden doorgegeven moeten top-level of statisch zijn - closures en instance methods falen bij serialisatie
  • Implementeer foutafhandeling binnen isolates en retourneer fouten als data om te verzekeren dat exceptions correct naar boven komen
  • Isolate pools verminderen spawn-overhead voor applicaties die veel onafhankelijke taken verwerken
  • De Flutter state management module vult concurrency-kennis aan voor sollicitatievoorbereiding

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Delen

Gerelateerde artikelen