Для создания независимых потоков выполнения в Dart/Flutter используются Изоляты (Isolates). Они представляют собой отдельные задачи с собственным выделенным пространством памяти, что предотвращает проблемы с конкурентным доступом к данным и блокировку основного потока (UI-потока).
Существует несколько способов создания и взаимодействия с изолятами:
Isolate.spawn(): Самый низкоуровневый способ создания изолята. Требует передачи функции верхнего уровня или статического метода класса для выполнения в новом изоляте и порта для обмена сообщениями.
import 'dart:isolate';
// Функция, выполняющаяся в изоляте
void isolateEntry(SendPort sendPort) {
// Выполняем какую-то долгую операцию
int result = 0;
for (int i = 0; i < 1000000000; i++) {
result += i;
}
// Отправляем результат обратно основному изоляту
sendPort.send(result);
}
void main() async {
ReceivePort receivePort = ReceivePort();
// Создаем новый изолят
Isolate isolate = await Isolate.spawn(isolateEntry, receivePort.sendPort);
// Слушаем сообщения от изолята
receivePort.listen((message) {
print('Результат из изолята: $message');
// Завершаем изолят после получения сообщения
isolate.kill();
});
print('Основной поток продолжает работу...');
}
compute(): Упрощенная функция из пакета flutter/foundation, которая использует Isolate.spawn() под капотом. Идеально подходит для простых фоновых задач, не требующих сложного взаимодействия с изолятом.
import 'package:flutter/foundation.dart';
// Функция, выполняющаяся с помощью compute
int heavyComputation(int limit) {
int result = 0;
for (int i = 0; i < limit; i++) {
result += i;
}
return result;
}
void main() async {
print('Начинаем тяжелые вычисления в фоне...');
// Выполняем функцию в отдельном изоляте
int result = await compute(heavyComputation, 1000000000);
print('Результат вычислений: $result');
print('Основной поток продолжает работу после вычислений.');
}
Isolate.spawnUri(): Используется для запуска кода из отдельного файла dart в новом изоляте.
Взаимодействие между изолятами:
Изоляты обмениваются данными только путем передачи сообщений через экземпляры SendPort и ReceivePort. Это атомарные операции, не требующие блокировок или других механизмов синхронизации.
SendPort: Используется для отправки сообщений из одного изолята в другой.
ReceivePort: Используется для прослушивания входящих сообщений.
При передаче объектов между изолятами, они должны быть "передаваемыми" (transferable), что включает в себя:
Простые типы данных (int, double, String, bool)
Списки и мапы, содержащие передаваемые типы
Некоторые специфические для Flutter типы, которые могут быть сериализованы
Объекты, не являющиеся передаваемыми, будут скопированы, а их последующие изменения в одном изоляте не отразятся в другом.
Ключевые моменты:
Изоляты не могут напрямую обращаться к памяти друг друга.
Изоляты не имеют доступа к общему состоянию, включая переменные, классы и виджеты основного изолята (UI-потока).
Общение между изолятами происходит асинхронно.
При работе с изолятами в Flutter важно правильно управлять их жизненным циклом (например, использовать isolate.kill() при необходимости).
Использование изолятов позволяет выполнять длительные или ресурсоемкие операции в фоне, предотвращая зависание пользовательского интерфейса и обеспечивая плавность работы приложения.