Dart Isolates e Concorrenza nel 2026: compute, Async e Domande da Colloquio
Padroneggiare Dart isolates, la funzione compute e i pattern async in Flutter. Modelli di concorrenza, ottimizzazione delle prestazioni e domande tecniche.

Gli isolates di Dart forniscono vera esecuzione parallela nelle applicazioni Flutter, risolvendo la limitazione single-threaded che causa scatti nell'interfaccia durante computazioni pesanti. A differenza dei thread in altri linguaggi, gli isolates non condividono memoria e comunicano esclusivamente tramite message passing, rendendo il codice concorrente piu sicuro e comprensibile.
Dart 3.7 ha introdotto isolates leggeri con il 50% di overhead di memoria ridotto. L'API Isolate.run() ora gestisce la maggior parte dei casi d'uso che in precedenza richiedevano lo spawn manuale degli isolates, mentre compute() rimane la scelta predefinita per i workload specifici di Flutter.
Comprendere il Modello di Concorrenza di Dart
Dart esegue il codice in un event loop single-threaded all'interno di ogni isolate. Questo significa che async/await non fornisce parallelismo: permette solo operazioni I/O non bloccanti. Quando un task CPU-intensive viene eseguito sull'isolate principale, l'interfaccia si blocca perche l'event loop non puo processare i callback dei frame.
La soluzione consiste nello spostare il lavoro pesante su isolates separati. Ogni isolate ha il proprio heap di memoria e il proprio event loop, eseguito su un thread separato gestito dalla VM Dart. La comunicazione avviene tramite SendPort e ReceivePort, che serializzano i messaggi tra gli isolates.
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
}La funzione Isolate.run() introdotta in Dart 2.19 semplifica il pattern comune di eseguire una singola computazione in un isolate in background. Gestisce automaticamente la creazione delle porte, la serializzazione dei messaggi e la dismissione dell'isolate.
Utilizzo di compute() nelle Applicazioni Flutter
Flutter fornisce la funzione compute() specificamente progettata per spostare il lavoro dall'isolate UI. A differenza delle API raw degli isolates, compute() si integra con il framework Flutter e gestisce il boilerplate della gestione degli isolates.
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);
}
}La funzione passata a compute() deve essere una funzione top-level o un metodo statico. I metodi di istanza e le closure catturano stato che non puo essere serializzato attraverso i confini degli isolates.
Passare oggetti non serializzabili agli isolates causa errori a runtime. Le classi devono essere semplici oggetti dati, senza BuildContext, senza riferimenti a Stream, senza singleton. Estrarre solo i dati primitivi necessari per la computazione.
Comunicazione Bidirezionale con le Porte
Scenari complessi richiedono comunicazione continua tra isolates piuttosto che un singolo ciclo richiesta-risposta. Task in background di lunga durata, elaborazione dati in streaming e pool di worker richiedono tutti message passing bidirezionale.
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};
}Questo pattern stabilisce un isolate worker persistente che accetta piu task nel tempo. L'isolate principale invia task tramite SendPort, e il worker risponde attraverso la propria porta. Lo stato all'interno dell'isolate worker persiste tra i task, abilitando caching ed elaborazione incrementale.
Async/Await vs Isolates: Scegliere lo Strumento Giusto
Una comune domanda da colloquio per sviluppatori Flutter riguarda quando usare async/await rispetto agli isolates. La distinzione e importante per le prestazioni dell'applicazione.
| Scenario | Usare async/await | Usare isolates | |----------|------------------|----------------| | Richieste di rete | Si | No | | I/O su file | Si | No | | Parsing JSON (piccolo) | Si | No | | Parsing JSON (>1MB) | No | Si | | Elaborazione immagini | No | Si | | Operazioni crittografiche | No | Si | | Query database | Si | A volte | | Calcoli complessi | No | Si |
Async/await gestisce le operazioni I/O-bound efficientemente perche la VM Dart delega l'attesa al sistema operativo. L'event loop rimane libero di processare gli eventi UI. Gli isolates risolvono problemi CPU-bound dove la computazione stessa consuma tempo indipendentemente dall'I/O.
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>;
}Pronto a superare i tuoi colloqui su Flutter?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Gestione degli Errori Attraverso i Confini degli Isolates
Gli errori lanciati negli isolates non si propagano automaticamente all'isolate chiamante. Senza una gestione appropriata, le eccezioni scompaiono silenziosamente, causando problemi difficili da debuggare nelle app in produzione.
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');
}Il pattern wrapper cattura le eccezioni all'interno dell'isolate e le restituisce come dati, assicurando che gli errori emergano nel codice chiamante. Questo approccio rispecchia il pattern Result usato in Rust e si allinea con le pratiche di gestione funzionale degli errori.
Ottimizzazione delle Prestazioni con Pool di Isolates
Lo spawn degli isolates comporta overhead: allocazione di memoria, compilazione JIT del codice nel nuovo isolate e serializzazione dei messaggi. Per applicazioni che processano molti task indipendenti, il riutilizzo degli isolates attraverso un pool migliora il throughput.
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());
});
}Il pool mantiene un numero fisso di isolates "riscaldati" pronti ad accettare lavoro. I task vengono messi in coda quando tutti gli isolates sono occupati, e gli isolates completati ritornano immediatamente al pool disponibile.
Domande Comuni da Colloquio sulla Concorrenza in Dart
I colloqui tecnici spesso verificano la comprensione del modello di concorrenza di Dart. La preparazione su queste domande dimostra una conoscenza approfondita oltre le basi dei widget Flutter. Il modulo domande colloquio Flutter copre argomenti aggiuntivi su widget e state management.
D: Cosa succede se si chiama setState() da un isolate?
Chiamare setState() da un isolate in background fallisce perche BuildContext e lo stato del widget esistono solo nella memoria dell'isolate principale. L'isolate non puo accedere direttamente agli oggetti dell'isolate principale. Invece, inviare il risultato calcolato all'isolate principale e chiamare setState() li:
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;
}D: Gli isolates possono condividere oggetti?
Gli isolates non possono condividere oggetti mutabili. Ogni isolate ha il proprio heap, e gli oggetti passati tra isolates vengono serializzati e copiati. Oggetti immutabili e primitivi possono essere trasferiti efficientemente perche Dart ottimizza il loro trasferimento. Lo stato mutabile richiede sincronizzazione esplicita tramite message passing.
D: Come funziona async/await con gli isolates?
Async/await all'interno di un isolate funziona in modo identico all'isolate principale: ogni isolate ha il proprio event loop. Attendere un Future in un isolate non blocca l'event loop dell'isolate principale. La funzione Isolate.run() stessa restituisce un Future che si completa quando l'isolate termina, integrandosi perfettamente con il codice async sull'isolate principale.
Debug del Codice Basato su Isolates
Il debug di applicazioni multi-isolate richiede la comprensione che breakpoint e logging si comportano diversamente attraverso i confini degli isolates.
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;
}La vista performance di Flutter DevTools mostra gli eventi della timeline da tutti gli isolates, permettendo di visualizzare l'esecuzione parallela e identificare i colli di bottiglia.
Il CPU Profiler in DevTools mostra quali isolates consumano piu tempo di elaborazione. Il flame chart separa l'esecuzione per isolate, rivelando se il lavoro e distribuito correttamente.
Conclusione
- Gli isolates forniscono vero parallelismo in Dart, eseguendo su thread separati con heap di memoria isolati
- Usare
Isolate.run()per semplici computazioni one-off,compute()per workload specifici di Flutter - Async/await gestisce il lavoro I/O-bound; gli isolates risolvono problemi CPU-bound che bloccano l'event loop
- Le funzioni passate agli isolates devono essere top-level o statiche: closure e metodi di istanza falliscono la serializzazione
- Implementare la gestione degli errori all'interno degli isolates e restituire gli errori come dati per assicurare che le eccezioni emergano correttamente
- I pool di isolates riducono l'overhead di spawn per applicazioni che processano molti task indipendenti
- Il modulo state management Flutter complementa la conoscenza sulla concorrenza per la preparazione ai colloqui
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Condividi
Articoli correlati

Ottimizzazione delle prestazioni di Flutter nel 2026: Impeller, ricostruzioni e best practice
Come mantenere le app Flutter a 60 o 120 fps costanti nel 2026 con Impeller, ricostruzioni di widget disciplinate, RepaintBoundary e profiling con DevTools.

Flutter Web vs React nel 2026: prestazioni, SEO e casi d'uso
Un confronto pratico del 2026 tra Flutter Web e React: come ciascuno esegue il rendering, i reali compromessi tra prestazioni e SEO, esempi di codice e quale scegliere per il progetto.

Flutter e Firebase nel 2026: Autenticazione, Firestore e Domande da Colloquio
Guida completa all'integrazione di Firebase in Flutter: autenticazione email/Google, operazioni CRUD su Firestore, real-time streams, sicurezza e pattern architetturali per il 2026.