Dart Isolates та Конкурентність у 2026: compute, Async та Питання на Співбесідах

Опануйте ізоляти Dart, функцію compute та async патерни для Flutter застосунків. Розглядаються моделі конкурентності, оптимізація продуктивності та типові питання на співбесідах з прикладами коду.

Візуалізація конкурентності ізолятів Dart з паралельними потоками та ізольованими потоками виконання

Ізоляти Dart забезпечують справжнє паралельне виконання у Flutter застосунках, вирішуючи проблему однопотокового обмеження, що спричиняє підлагування UI під час інтенсивних обчислень. На відміну від потоків в інших мовах, ізоляти не ділять пам'ять і спілкуються виключно через передачу повідомлень, що робить конкурентний код безпечнішим і простішим для розуміння.

Ключова Інформація на 2026

Dart 3.7 представив легкі ізоляти зі зменшенням використання пам'яті на 50%. API Isolate.run() тепер обробляє більшість випадків використання, які раніше вимагали ручного створення ізолятів, тоді як compute() залишається переважним вибором для Flutter-специфічних навантажень.

Розуміння Моделі Конкурентності Dart

Dart виконує код в однопотоковому циклі подій всередині кожного ізоляту. Це означає, що async/await не забезпечує паралелізму — воно лише дозволяє неблокуючі I/O операції. Коли CPU-інтенсивна задача виконується на головному ізоляті, UI замерзає, оскільки цикл подій не може обробляти callback'и кадрів.

Рішення полягає у винесенні важкої роботи до окремих ізолятів. Кожен ізолят має власну купу пам'яті та цикл подій, що працюють на окремому потоці, керованому Dart VM. Комунікація відбувається через SendPort та ReceivePort, які серіалізують повідомлення між ізолятами.

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
}

Функція Isolate.run(), представлена в Dart 2.19, спрощує типовий патерн запуску одиничного обчислення у фоновому ізоляті. Вона автоматично обробляє створення портів, серіалізацію повідомлень та знищення ізоляту.

Використання compute() у Flutter Застосунках

Flutter надає функцію compute(), спеціально розроблену для винесення роботи з UI ізоляту. На відміну від сирих API ізолятів, compute() інтегрується з фреймворком Flutter та обробляє шаблонний код управління ізолятами.

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);
  }
}

Функція, передана до compute(), має бути функцією верхнього рівня або статичним методом. Методи екземплярів та замикання захоплюють стан, який не може бути серіалізований через межі ізолятів.

Поширена Помилка

Передача несеріалізовуваних об'єктів до ізолятів спричиняє помилки виконання. Класи мають бути простими об'єктами даних — без BuildContext, без посилань на Stream, без синглтонів. Витягуйте лише примітивні дані, необхідні для обчислення.

Двонаправлена Комунікація з Портами

Складні сценарії вимагають постійної комунікації між ізолятами, а не одиничного циклу запит-відповідь. Довготривалі фонові задачі, потокова обробка даних та пули воркерів — усі потребують двонаправленої передачі повідомлень.

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};
}

Цей патерн встановлює постійний ізолят воркера, який приймає множинні задачі з часом. Головний ізолят відправляє задачі через SendPort, а воркер відповідає через власний порт. Стан всередині ізоляту воркера зберігається між задачами, уможливлюючи кешування та інкрементальну обробку.

Async/Await проти Ізолятів: Вибір Правильного Інструменту

Поширеним питанням на співбесіді для Flutter розробників є коли використовувати async/await, а коли ізоляти. Це розрізнення важливе для продуктивності застосунку.

| Сценарій | Використовуйте async/await | Використовуйте ізоляти | |----------|-----------------|-------------| | Мережеві запити | Так | Ні | | Файловий I/O | Так | Ні | | Парсинг JSON (малий) | Так | Ні | | Парсинг JSON (>1MB) | Ні | Так | | Обробка зображень | Ні | Так | | Криптографічні операції | Ні | Так | | Запити до бази даних | Так | Іноді | | Складні обчислення | Ні | Так |

Async/await ефективно обробляє I/O-bound операції, оскільки Dart VM делегує очікування операційній системі. Цикл подій залишається вільним для обробки UI подій. Ізоляти вирішують CPU-bound проблеми, де саме обчислення споживає час незалежно від 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>;
}

Готовий до співбесід з Flutter?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Обробка Помилок Через Межі Ізолятів

Помилки, кинуті в ізолятах, автоматично не поширюються до викликаючого ізоляту. Без належної обробки винятки зникають тихо, спричиняючи важко відстежувані проблеми у продакшн застосунках.

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');
}

Патерн обгортки перехоплює винятки всередині ізоляту та повертає їх як дані, забезпечуючи видимість помилок у викликаючому коді. Цей підхід відображає патерн Result, використовуваний у Rust та узгоджується з практиками функціональної обробки помилок.

Оптимізація Продуктивності з Пулами Ізолятів

Створення ізолятів має накладні витрати — виділення пам'яті, JIT компіляція коду в новому ізоляті та серіалізація повідомлень. Для застосунків, що обробляють багато незалежних задач, повторне використання ізолятів через пул покращує пропускну здатність.

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());
  });
}

Пул підтримує фіксовану кількість прогрітих ізолятів, готових приймати роботу. Задачі стають у чергу, коли всі ізоляти зайняті, а завершені ізоляти негайно повертаються до доступного пулу.

Типові Питання на Співбесідах про Конкурентність Dart

Технічні співбесіди часто перевіряють розуміння моделі конкурентності Dart. Підготовка до цих питань демонструє глибину знань, що виходить за межі базового знання Flutter віджетів. Модуль питань на співбесіді Flutter охоплює додаткові теми віджетів та управління станом.

П: Що станеться, якщо викликати setState() з ізоляту?

Виклик setState() з фонового ізоляту не вдасться, оскільки BuildContext та стан віджета існують лише в пам'яті головного ізоляту. Ізолят не може безпосередньо отримати доступ до об'єктів головного ізоляту. Натомість відправте обчислений результат назад до головного ізоляту і викличте setState() там:

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;
}

П: Чи можуть ізоляти ділити об'єкти?

Ізоляти не можуть ділити змінювані об'єкти. Кожен ізолят має власну купу, і об'єкти, передані між ізолятами, серіалізуються та копіюються. Незмінні об'єкти та примітиви можуть передаватися ефективно, оскільки Dart оптимізує їх передачу. Змінюваний стан вимагає явної синхронізації через передачу повідомлень.

П: Як async/await працює з ізолятами?

Async/await всередині ізоляту працює ідентично до головного ізоляту — кожен ізолят має власний цикл подій. Очікування Future в ізоляті не блокує цикл подій головного ізоляту. Сама функція Isolate.run() повертає Future, що завершується коли ізолят закінчує роботу, безшовно інтегруючись з async кодом у головному ізоляті.

Налагодження Коду на Основі Ізолятів

Налагодження багатоізолятних застосунків вимагає розуміння того, що точки зупинки та логування поводяться по-різному через межі ізолятів.

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;
}

Вид продуктивності Flutter DevTools показує події таймлайну з усіх ізолятів, уможливлюючи візуалізацію паралельного виконання та ідентифікацію вузьких місць.

Підказка DevTools

CPU Profiler у DevTools допомагає визначити, які ізоляти споживають найбільше часу обробки. Flame chart розділяє виконання за ізолятами, показуючи чи робота правильно розподілена.

Висновок

  • Ізоляти забезпечують справжній паралелізм у Dart, працюючи на окремих потоках з ізольованими купами пам'яті
  • Використовуйте Isolate.run() для простих одноразових обчислень, compute() для Flutter-специфічних навантажень
  • Async/await обробляє I/O-bound роботу; ізоляти вирішують CPU-bound проблеми, що блокують цикл подій
  • Функції, передані до ізолятів, мають бути верхнього рівня або статичні — замикання та методи екземплярів не проходять серіалізацію
  • Реалізуйте обробку помилок всередині ізолятів і повертайте помилки як дані, щоб забезпечити належне виявлення винятків
  • Пули ізолятів зменшують накладні витрати створення для застосунків, що обробляють багато незалежних задач
  • Модуль управління станом Flutter доповнює знання конкурентності для підготовки до співбесід

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#dart
#flutter
#isolates
#concurrency
#async
#performance

Поділитися

Пов'язані статті