← Назад к вопросам

Можно ли в изолят передать класс?

3.0 Senior🔥 71 комментариев
#Dart#Асинхронность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Передача класса в изолят

Отличный вопрос о одной из самых сложных особенностей Dart изолятов. Коротко: НЕТ, нельзя передать класс как есть. Давайте разберемся почему и как это обойти.

Основная проблема

Дартовский изолят — это полностью независимый поток выполнения с отдельной памятью и Garbage Collector. Классы (объекты) хранятся в памяти и имеют состояние, которое нельзя просто так скопировать между изолятами.

// Неправильно: так не работает!
class User {
  final String name;
  User(this.name);
}

void main() async {
  final user = User('John');
  final receivePort = ReceivePort();
  
  // ОШИБКА: нельзя передать объект класса!
  await Isolate.spawn(
    _isolateTask,
    user, // Это не сработает!
  );
}

void _isolateTask(User user) {
  print(user.name);
}

Результат: RuntimeError - нельзя сериализовать произвольные классы между изолятами.

Что можно передавать в изолят

1. Примитивные типы (работают всегда)

void main() async {
  final receivePort = ReceivePort();
  
  await Isolate.spawn(
    _isolateTask,
    {
      'name': 'John',           // String ✅
      'age': 30,                // int ✅
      'salary': 50000.5,        // double ✅
      'active': true,           // bool ✅
      'data': [1, 2, 3],        // List ✅
      'map': {'key': 'value'},  // Map ✅
    },
  );
}

void _isolateTask(Map<String, dynamic> params) {
  print(params['name']);
  print(params['age']);
}

2. Класс с аннотацией @pragma('vm:entry-point')

Для классов, которые должны быть доступны в изолятах:

@pragma('vm:entry-point')
class User {
  final String name;
  final int age;
  
  User({required this.name, required this.age});
  
  // Метод для сериализации
  Map<String, dynamic> toMap() {
    return {'name': name, 'age': age};
  }
  
  // Фабрика для десериализации
  factory User.fromMap(Map<String, dynamic> map) {
    return User(
      name: map['name'] as String,
      age: map['age'] as int,
    );
  }
}

void main() async {
  final user = User(name: 'John', age: 30);
  final receivePort = ReceivePort();
  
  // Передаём сериализованные данные
  await Isolate.spawn(
    _isolateTask,
    user.toMap(), // Передаём Map, не объект!
  );
}

void _isolateTask(Map<String, dynamic> userData) {
  final user = User.fromMap(userData);
  print('User: ${user.name}, Age: ${user.age}');
}

3. SendPort для двусторонней коммуникации

Это способ передачи порта между изолятами:

void main() async {
  final receivePort = ReceivePort();
  
  // Передаём SendPort для ответа
  await Isolate.spawn(
    _isolateTask,
    receivePort.sendPort, // SendPort ✅
  );
  
  // Слушаем ответ
  receivePort.listen((message) {
    print('Ответ из изолята: $message');
  });
}

void _isolateTask(SendPort sendPort) {
  // Отправляем результат назад
  sendPort.send('Hello from isolate');
}

Правильный способ: Паттерн Request-Response

Это наиболее эффективный подход для сложных задач:

class UserService {
  final String name;
  final String email;
  
  UserService({required this.name, required this.email});
  
  // Сериализуем для передачи
  Map<String, dynamic> toMap() {
    return {'name': name, 'email': email};
  }
  
  factory UserService.fromMap(Map<String, dynamic> map) {
    return UserService(
      name: map['name'],
      email: map['email'],
    );
  }
}

void main() async {
  final receivePort = ReceivePort();
  
  final userData = {
    'user': UserService(name: 'John', email: 'john@example.com').toMap(),
    'sendPort': receivePort.sendPort,
  };
  
  await Isolate.spawn(_isolateTask, userData);
  
  // Получаем результат
  final result = await receivePort.first;
  print('Результат: $result');
}

void _isolateTask(Map<String, dynamic> params) {
  final sendPort = params['sendPort'] as SendPort;
  final userData = params['user'] as Map<String, dynamic>;
  
  final user = UserService.fromMap(userData);
  
  // Обработка в изолате
  final result = 'Обработал ${user.name}';
  
  // Отправляем ответ
  sendPort.send(result);
}

Современный подход: compute() функция

Для простых задач лучше использовать compute() из flutter:

import 'package:flutter/foundation.dart';

// Функция должна быть top-level или static
Future<String> expensiveComputation(Map<String, dynamic> params) async {
  final name = params['name'] as String;
  // Тяжелая работа
  await Future.delayed(Duration(seconds: 2));
  return 'Processed: $name';
}

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () async {
        // Простой способ использовать изолят
        final result = await compute(
          expensiveComputation,
          {'name': 'John'},
        );
        print(result);
      },
      child: Text('Run Computation'),
    );
  }
}

Преимущества compute():

  • Автоматически создает и управляет изолятом
  • Простой синтаксис
  • Нет нужды вручную работать с SendPort/ReceivePort
  • Идеально для обработки данных

Практический пример: Обработка JSON в изолате

class ApiResponse {
  final int statusCode;
  final String body;
  
  ApiResponse(this.statusCode, this.body);
  
  Map<String, dynamic> toMap() => {
    'statusCode': statusCode,
    'body': body,
  };
  
  factory ApiResponse.fromMap(Map<String, dynamic> map) {
    return ApiResponse(map['statusCode'], map['body']);
  }
}

// Тяжелая работа в изолате
Future<Map<String, dynamic>> parseJsonInIsolate(
  Map<String, dynamic> params,
) async {
  final response = ApiResponse.fromMap(params['response']);
  
  // Парсим большой JSON
  final jsonData = jsonDecode(response.body);
  
  // Сложная обработка
  final processed = jsonData.map((item) => {
    'id': item['id'],
    'name': item['name'],
    'processed': true,
  }).toList();
  
  return {'success': true, 'data': processed};
}

future<void> main() async {
  final response = ApiResponse(200, jsonEncode([
    {'id': 1, 'name': 'John'},
    {'id': 2, 'name': 'Jane'},
  ]));
  
  final result = await compute(
    parseJsonInIsolate,
    {'response': response.toMap()},
  );
  
  print(result);
}

Сравнение подходов

ПодходСложностьПроизводительностьРекомендуется для
Примитивные типыНизкаяОтличнаяПростые данные
compute()НизкаяХорошаяОбработка данных
SendPort/ReceivePortСредняяОтличнаяДвусторонняя коммуникация
Serializable классыСредняяХорошаяСложные объекты

Key Takeaways

Передавайте:

  • Примитивные типы (int, String, bool, double)
  • List и Map с примитивными значениями
  • SendPort для коммуникации
  • Сериализованные данные (toMap(), JSON)

НЕ передавайте:

  • Объекты произвольных классов
  • Функции и closures
  • Контексты (BuildContext, DatabaseConnection)
  • Файловые дескрипторы

Правило золотого стандарта: Передавайте в изолят простые типы, сериализуйте сложные объекты в Map/JSON, используйте SendPort для ответов.

Можно ли в изолят передать класс? | PrepBro