DMarlon Studio

Lección 17 · 17 de 20

Isolates con Isolate.run · Dart

Saque el cálculo pesado del hilo principal en Dart con Isolate.run, ejecute varios isolates en paralelo y capture sus errores.

Dart · Isolates con Isolate.run en DMarlon Studio: el código del ejemplo y su salida en la terminal

Todo código de Dart se ejecuta dentro de un isolate: una unidad de ejecución con su propia memoria y su propio bucle de eventos. async/await sirve para esperar sin bloquear, pero no reparte el cálculo: si una función pasa un segundo calculando, el isolate principal se queda ocupado ese segundo y, en una aplicación Flutter, la interfaz se congela.

countPrimes es trabajo de CPU puro: una criba de Eratóstenes que marca los números compuestos en una lista de booleanos y cuenta los que quedan. `Isolate.run`, de dart:isolate, crea un isolate nuevo, ejecuta en él la función que recibe, devuelve el resultado como una Future y cierra el isolate. Mientras tanto, el isolate principal queda libre.

Varios Isolate.run a la vez aprovechan varios núcleos. Pueden terminar en cualquier orden, pero Future.wait devuelve los resultados en el orden de la lista y el programa los escribe al final, por eso la salida es siempre la misma. Los isolates no comparten memoria: lo que se envía y lo que vuelve se copia, así que no hay condiciones de carrera de las que preocuparse.

Si la función falla dentro del isolate, el error vuelve a quien llamó: countPrimes(-1) intenta crear una lista de tamaño negativo, lanza RangeError y el try/catch de main lo captura. En Flutter, la función compute() hace exactamente esto, y es la forma de procesar un JSON enorme o una imagen sin que la pantalla se detenga.

Código · main.dart

import 'dart:isolate';// CPU-heavy work: count the primes below [limit] with a sieve.int countPrimes(int limit) {  final composite = List<bool>.filled(limit, false);  var count = 0;  for (var n = 2; n < limit; n++) {    if (composite[n]) continue;    count++;    for (var m = n * n; m < limit; m += n) composite[m] = true;  }  return count;}Future<void> main() async {  // Isolate.run runs the function in another isolate (another core)  // and returns its result as a Future: main stays free meanwhile.  final primes = await Isolate.run(() => countPrimes(5000000));  print('Primes below 5,000,000: $primes');  // Several isolates at once; Future.wait keeps the order of the list.  final limits = [1000, 100000, 1000000];  final counts = await Future.wait([for (final n in limits) Isolate.run(() => countPrimes(n))]);  for (var i = 0; i < limits.length; i++) {    print('Below ${limits[i]}: ${counts[i]}');  }  // An exception inside the isolate comes back to the caller.  try {    await Isolate.run(() => countPrimes(-1));  } on RangeError {    print('The worker failed with a RangeError');  }}

Salida · dart run main.dart

Primes below 5,000,000: 348513
Below 1000: 168
Below 100000: 9592
Below 1000000: 78498
The worker failed with a RangeError

Para recordar

  • Cada isolate tiene su propia memoria: no comparten variables.
  • async no reparte el cálculo; otro isolate sí.
  • Isolate.run ejecuta una función en otro isolate y devuelve una Future.
  • Con Future.wait, los resultados siguen el orden de la lista.
  • Los errores del isolate se capturan con try/catch en quien espera.

Ejercicio

Escriba una función sumOfSquares(int n) que sume los cuadrados de 1 a n, y ejecute en paralelo n = 1000 y n = 1000000 con Isolate.run.