Concurență Reală cu Isolates în Dart: Isolate.run, Isolate.spawn și Canale SendPort / ReceivePort în Dart

Isolate: calcul în paralel Dart 2.19+ / 3.0+

Ghid de concurență paralelă în Dart: diferențele între execuția asincronă pe un singur fir și paralelismul real cu Isolate, utilizarea Isolate.run pentru calcule scurte și Isolate.spawn cu SendPort/ReceivePort pentru worker threads continui.

Sintaxă
final result = await Isolate.run(() => heavyComputation(data));

O confuzie frecventă în rândul dezvoltatorilor este echivalarea operațiunilor async / await cu rularea pe fire paralele. În Dart, codul asincron standard rulează pe același fir principal de execuție (Event Loop). Dacă executați o operațiune intensivă CPU (cum ar fi redimensionarea unei imagini, parsarea unui fișier JSON de 50 MB sau criptarea datelor), firul principal este complet blocat, iar aplicația UI (Flutter) „îngheață” (*UI jank*).

Pentru a rula calcule paralele pe nucleele fizice ale procesorului, Dart oferă Isolates. Fiecare isolate reprezintă un fir de execuție complet independent, cu propriul său heap de memorie. Nicio variabilă nu este partajată direct, eliminând total necesitatea blocărilor de tip mutex sau riscul apariției condițiilor de cursă (*data races*).

Modelul Simplificat: Isolate.run() pentru Sarcini CPU Scurte
import 'dart:isolate';

// Funcție CPU-intensivă:
int calculateFibonacci(int n) {
  if (n <= 1) return n;
  return calculateFibonacci(n - 1) + calculateFibonacci(n - 2);
}

Future main() async {
  print('Pornire calcul pe Isolate secundar...');

  // Isolate.run creează un isolate nou, rulează funcția pe un nucleu separat
  // și returnează rezultatul asincron către firul principal, oprind isolate-ul automat:
  final result = await Isolate.run(() => calculateFibonacci(42));

  print('Rezultat obținut fără a bloca firul principal: $result');
}

Comparație: Isolate.run vs Isolate.spawn

Criteriu Tehnic Isolate.run() Isolate.spawn() cu Porturi
Ciclu de Viață Efemer (pornește, calculează un singur rezultat și se distruge) Persistent (rămâne activ ca un serviciu worker continuu)
Model de Comunicare Returnare directă asincronă via Future Canale bidirecționale de mesaje via SendPort și ReceivePort
Overhead de Inițiere Mic pentru calcule izolate, dar repetat la fiecare apel Overhead plătit o singură dată la pornirea worker-ului
Scenariu Optim Parsare JSON mare, calcul hash unic, procesare fișier Isolate Pool, procesare continuă de stream-uri audio/video, baze de date
Isolate Persistent cu Canale Bidirecționale (SendPort / ReceivePort)
import 'dart:isolate';

// Punctul de intrare al Worker-ului Isolate:
void workerEntryPoint(SendPort sendToMain) {
  final receiveInWorker = ReceivePort();
  // Trimitem portul worker-ului înapoi către firul principal:
  sendToMain.send(receiveInWorker.sendPort);

  // Ascultăm comenzi primite de la firul principal:
  receiveInWorker.listen((message) {
    if (message is (int taskId, String payload)) {
      final processed = payload.toUpperCase();
      sendToMain.send((taskId, processed));
    }
  });
}

Future main() async {
  final receiveFromWorker = ReceivePort();
  await Isolate.spawn(workerEntryPoint, receiveFromWorker.sendPort);

  // Obținem portul de trimitere către worker din primul mesaj:
  final sendToWorker = await receiveFromWorker.first as SendPort;

  // Cream un canal dedicat pentru a primi răspunsul:
  final responsePort = ReceivePort();
  sendToWorker.send((101, 'text de procesat'));

  receiveFromWorker.listen((message) {
    print('Răspuns primit din Isolate-ul paralel: $message');
  });
}