跳转到正文
YGJoon 技术博客

Dart 隔离区(Isolate)详解

发布于:
·1.5k 字 · 约 4 分钟·作者:
Isolate 不是线程:讲清创建、SendPort 与 ReceivePort 双向通信、可传递的数据类型,以及耗时计算扔出 UI 线程的最佳实践。
Dart 隔离区(Isolate)详解文章封面
目录

简介

Isolate 是 Dart 中实现并发的方式。每个 Isolate 都有自己的内存堆和事件循环,Isolate 之间通过消息传递进行通信。

基本概念

创建 Isolate

import 'dart:isolate';

void isolateFunction(SendPort sendPort) {
  // Isolate 中的计算
  int result = complexComputation();
  // 发送结果回主 Isolate
  sendPort.send(result);
}

Future<void> main() async {
  // 创建通信端口
  ReceivePort receivePort = ReceivePort();
  
  // 启动新的 Isolate
  await Isolate.spawn(isolateFunction, receivePort.sendPort);
  
  // 接收结果
  final result = await receivePort.first;
  print('Result: $result');
}

双向通信

import 'dart:async';
import 'dart:isolate';

void isolateFunction(SendPort sendPort) {
  // 创建接收端口
  final receivePort = ReceivePort();

  // 发送此 Isolate 的 SendPort 到主 Isolate
  sendPort.send(receivePort.sendPort);

  // 处理接收到的消息
  receivePort.listen((message) {
    if (message is int) {
      final result = message * 2;
      sendPort.send(result);
    }
  });
}

Future<void> main() async {
  final receivePort = ReceivePort();
  final isolate = await Isolate.spawn(isolateFunction, receivePort.sendPort);

  // 注意:ReceivePort 是单订阅流,只能 listen 一次,
  // 不能像普通变量那样反复 await receivePort.first。
  // 所以用一次订阅同时处理两件事:收到对方的 SendPort 就发请求,收到数字回包就结束等待
  final completer = Completer<int>();
  receivePort.listen((message) {
    if (message is SendPort) {
      message.send(42);
    } else if (message is int) {
      completer.complete(message);
    }
  });

  final result = await completer.future;
  print('Result: $result'); // 84
  receivePort.close();
  isolate.kill();
}

数据传递

可传递的数据类型

// 基本类型
sendPort.send(42);
sendPort.send('Hello');
sendPort.send(true);

// 集合
sendPort.send([1, 2, 3]);
sendPort.send({'a': 1, 'b': 2});

// SendPort
sendPort.send(anotherSendPort);

// 自定义类型(需要实现特定接口)
class TransferableData {
  final int id;
  final String data;
  
  TransferableData(this.id, this.data);
  
  // 转换为可传输格式
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'data': data,
    };
  }
  
  // 从可传输格式恢复
  factory TransferableData.fromMap(Map<String, dynamic> map) {
    return TransferableData(
      map['id'] as int,
      map['data'] as String,
    );
  }
}

错误处理

异常捕获

import 'dart:isolate';

void isolateFunction(SendPort sendPort) {
  try {
    throw Exception('Something went wrong');
  } catch (e) {
    sendPort.send({'error': e.toString()});
  }
}

Future<void> main() async {
  final receivePort = ReceivePort();
  
  try {
    await Isolate.spawn(isolateFunction, receivePort.sendPort);
    final result = await receivePort.first;
    
    if (result is Map && result.containsKey('error')) {
      print('Error in isolate: ${result['error']}');
    }
  } catch (e) {
    print('Error spawning isolate: $e');
  }
}

完整示例

import 'dart:isolate';
import 'dart:math';

// 计算密集型任务
class ComputeTask {
  final int start;
  final int end;
  
  ComputeTask(this.start, this.end);
  
  Map<String, dynamic> toMap() => {
    'start': start,
    'end': end,
  };
  
  factory ComputeTask.fromMap(Map<String, dynamic> map) => 
    ComputeTask(map['start'], map['end']);
}

// 计算结果
class ComputeResult {
  final int sum;
  final Duration duration;
  
  ComputeResult(this.sum, this.duration);
  
  Map<String, dynamic> toMap() => {
    'sum': sum,
    'duration': duration.inMilliseconds,
  };
  
  factory ComputeResult.fromMap(Map<String, dynamic> map) => 
    ComputeResult(
      map['sum'],
      Duration(milliseconds: map['duration']),
    );
}

// Isolate 工作函数
void computeIsolate(SendPort sendPort) {
  final receivePort = ReceivePort();
  sendPort.send(receivePort.sendPort);
  
  receivePort.listen((message) {
    // 消息协议:[任务 Map, 本次请求的专属回复端口]
    if (message is List && message.length == 2) {
      final task = ComputeTask.fromMap(message[0] as Map<String, dynamic>);
      final replyTo = message[1] as SendPort;
      final stopwatch = Stopwatch()..start();
      
      // 执行计算密集型任务
      int sum = 0;
      for (int i = task.start; i <= task.end; i++) {
        sum += i;
        // 模拟复杂计算
        sum += sqrt(i).floor();
      }
      
      stopwatch.stop();
      final result = ComputeResult(sum, stopwatch.elapsed);
      // 回复发给这次请求的专属端口,而不是 spawn 时的端口
      replyTo.send(result.toMap());
    }
  });
}

class ComputeManager {
  SendPort? _sendPort;
  ReceivePort? _receivePort;
  Isolate? _isolate;
  
  Future<void> initialize() async {
    _receivePort = ReceivePort();
    _isolate = await Isolate.spawn(computeIsolate, _receivePort!.sendPort);
    _sendPort = await _receivePort!.first;
  }
  
  Future<ComputeResult> compute(ComputeTask task) async {
    if (_sendPort == null) {
      throw StateError('ComputeManager not initialized');
    }
    
    // 每个请求配一个专属回复端口:响应只会回到这里,多任务并发也不会串消息。
    // 注意不能直接 await _receivePort.first —— ReceivePort 是单订阅流,
    // initialize() 里已经 listen 过一次,再订阅会抛 "Stream has already been listened to"
    final responsePort = ReceivePort();
    _sendPort!.send([task.toMap(), responsePort.sendPort]);
    final resultMap = (await responsePort.first) as Map<String, dynamic>;
    responsePort.close();
    return ComputeResult.fromMap(resultMap);
  }
  
  void dispose() {
    _receivePort?.close();
    _isolate?.kill();
    _sendPort = null;
    _receivePort = null;
    _isolate = null;
  }
}

void main() async {
  final manager = ComputeManager();
  await manager.initialize();
  
  // 执行多个计算任务
  final tasks = [
    ComputeTask(1, 1000000),
    ComputeTask(1000001, 2000000),
    ComputeTask(2000001, 3000000),
  ];
  
  for (var task in tasks) {
    try {
      final result = await manager.compute(task);
      print('Task ${task.start}-${task.end}:');
      print('Sum: ${result.sum}');
      print('Duration: ${result.duration.inMilliseconds}ms\n');
    } catch (e) {
      print('Error computing task: $e');
    }
  }
  
  manager.dispose();
}

最佳实践

  1. 合理划分计算任务
  2. 注意数据传输开销
  3. 及时释放资源
  4. 处理好异常情况
  5. 避免过多的 Isolate

注意事项

  1. Isolate 之间不共享内存
  2. 注意数据序列化成本
  3. 合理控制 Isolate 数量
  4. 处理好生命周期
  5. 注意平台限制

实战视角:我的项目为什么没用 Isolate

说实话,我的几个 Flutter 项目里(flutter_brightness、某物流冷链项目、ygking_json_to_dart_model),一个都没用过 Isolate.spawn。这不是因为我不懂,而是因为都没到那个份上:

什么时候才需要 Isolate?记住这条线(参考了 Medium 上一篇 Dart 并发指南的决策框架,改写成我的话):

  1. IO 等待(网络、文件、串口)→ async/await 就够,根本不用 Isolate。IO 不占 CPU,只是在”等”。
  2. CPU 密集且一次性的(大 JSON 解析、图片压缩)→ compute(),Flutter 给你包好的”一次性后台线程”。
  3. CPU 密集且长期存在的(音视频编解码后台任务)→ 才轮到 Isolate.spawn 自己管理。

新手最常见的误区是把”异步”和”多线程”混为一谈:await 不等于”换了个线程跑”,它只是在等的时候把线程让出来。先分清你的任务是”等”还是”算”——是”等”,async 就够;是”算”且超过一帧(16ms),再考虑 Isolate。我的项目全是”等”,所以一个 Isolate 都没写,这恰恰说明决策是对的。

参考:Medium《Mastering Dart Concurrency》

总结

Isolate 的心智模型其实就一句话:不共享内存,只传消息。上面完整示例里那个 [任务, 回复端口] 的消息协议,就是这句话的落地——连”回复发给谁”都得显式传过去,没有任何隐式共享。记住这个,Isolate 的 API 就剩不下多少东西了。


分享这篇文章:


相关文章

上一篇
一篇文章让你对 Dart 有初步了解
下一篇
Dart 反射和镜像详解