Isolate 并发编程
Dart 是单线程语言,所有代码默认运行在主 Isolate 中。当遇到耗时计算时,如果直接在主 Isolate 中执行,会导致 UI 卡顿。Isolate 是 Dart 提供的并发方案,可以让耗时任务在独立线程中运行,不阻塞 UI。
Isolate 是什么?
Isolate 可以理解为 Dart 中的"线程",但它与操作系统线程有重要区别:
| 特性 | Isolate | 操作系统线程 |
|---|---|---|
| 内存 | 各自独立,不共享内存 | 共享内存 |
| 通信 | 通过消息传递(SendPort / ReceivePort) | 通过共享内存 |
| 锁 | 不需要锁 | 需要加锁防止竞态 |
| 开销 | 比线程轻量 | 较重 |
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ 主 Isolate │ │ 子 Isolate │
│ ┌───────────┐ ┌───────────┐ │ │ ┌───────────┐ ┌───────────┐ │
│ │ UI 渲染 │ │ 事件处理 │ │ │ │ 耗时计算 │ │ 文件解析 │ │
│ └───────────┘ └───────────┘ │ │ └───────────┘ └───────────┘ │
│ │ │ │
│ ReceivePort ◄──── 消息 ──── SendPort │ SendPort ──── 消息 ────► ReceivePort │
└─────────────────────────────────┘ └─────────────────────────────────┘核心概念
Isolate 之间不共享内存,只能通过 SendPort / ReceivePort 传递消息。这是 Isolate 与线程最大的区别,也是它不需要锁的原因。
何时需要 Isolate?
需要 Isolate 的场景
- 解析大型 JSON(如几 MB 的配置文件)
- 图片处理(压缩、裁剪、滤镜)
- 加密 / 解密操作
- 大量数学计算(排序、搜索)
- 文件读写(大文件)
不需要 Isolate 的场景
- 网络请求 ——
http/dio内部已异步处理,不阻塞 UI - 简单的数据转换 ——
json.decode小数据量很快 - 读写 SharedPreferences —— 数据量小,速度够快
- 数据库操作 —— 大多数 ORM 已异步封装
如何判断?
如果耗时操作超过 16 毫秒(一帧的时间),就可能影响 UI 流畅度,应考虑使用 Isolate。
Isolate.spawn:创建子 Isolate
Isolate.spawn 是最基础的创建 Isolate 方式。
基本用法
import 'dart:isolate';
// 子 Isolate 的入口函数
// 必须是顶层函数或静态方法
void heavyTask(SendPort sendPort) {
int result = 0;
for (int i = 0; i < 100000000; i++) {
result += i;
}
sendPort.send(result); // 通过 SendPort 发送结果
}
Future<void> main() async {
// 1. 创建 ReceivePort 接收消息
final receivePort = ReceivePort();
// 2. 创建子 Isolate
await Isolate.spawn(heavyTask, receivePort.sendPort);
// 3. 监听子 Isolate 发来的消息
receivePort.listen((message) {
print('计算结果: $message'); // 计算结果: 4999999950000000
receivePort.close(); // 用完关闭
});
}双向通信
子 Isolate 也可以向主 Isolate 发送消息,需要两对 SendPort / ReceivePort:
import 'dart:isolate';
void worker(SendPort mainSendPort) {
// 子 Isolate 创建自己的 ReceivePort
final workerReceivePort = ReceivePort();
// 把自己的 SendPort 发给主 Isolate
mainSendPort.send(workerReceivePort.sendPort);
// 监听主 Isolate 发来的消息
workerReceivePort.listen((message) {
if (message is String && message == 'start') {
final result = doHeavyWork();
mainSendPort.send(result);
}
});
}
int doHeavyWork() {
int sum = 0;
for (int i = 0; i < 100000000; i++) {
sum += i;
}
return sum;
}
Future<void> main() async {
final mainReceivePort = ReceivePort();
await Isolate.spawn(worker, mainReceivePort.sendPort);
// 先收到子 Isolate 的 SendPort
SendPort? workerSendPort;
mainReceivePort.listen((message) {
if (message is SendPort) {
// 第一次收到的是子 Isolate 的 SendPort
workerSendPort = message;
// 向子 Isolate 发送任务
workerSendPort!.send('start');
} else {
// 收到计算结果
print('计算结果: $message');
mainReceivePort.close();
}
});
}握手模式
双向通信的标准模式:主 Isolate 先把自己的 SendPort 传给子 Isolate,子 Isolate 再把它的 SendPort 回传。这叫做"握手",是 Isolate 双向通信的基础。
compute:简化版 Isolate
Flutter 提供了 compute 函数,它是 Isolate.spawn 的高层封装,使用更简单。
基本用法
import 'package:flutter/foundation.dart';
// 耗时函数
int heavyCalculation(int limit) {
int sum = 0;
for (int i = 0; i < limit; i++) {
sum += i;
}
return sum;
}
Future<void> main() async {
// compute(函数, 参数) —— 一步到位
final result = await compute(heavyCalculation, 100000000);
print('计算结果: $result');
}compute vs Isolate.spawn
| 特性 | compute | Isolate.spawn |
|---|---|---|
| 使用难度 | 简单,一行代码 | 需要手动管理 Port |
| 返回值 | 直接返回结果 | 需要通过 Port 接收 |
| 双向通信 | 不支持 | 支持 |
| 多次通信 | 不支持 | 支持 |
| 生命周期控制 | 自动管理 | 需手动关闭 |
| 适用场景 | 一次性计算任务 | 复杂的持续通信场景 |
推荐
- 大多数场景用
compute—— 简单高效,自动管理生命周期 - 需要双向通信或多次交互时用
Isolate.spawn—— 更灵活
在 Widget 中使用 compute
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
// 耗时计算函数(必须是顶层函数或静态方法)
List<int> sortLargeList(List<int> data) {
data.sort();
return data;
}
class SortPage extends StatefulWidget {
const SortPage({super.key});
@override
State<SortPage> createState() => _SortPageState();
}
class _SortPageState extends State<SortPage> {
List<int> _data = [];
bool _isSorting = false;
Future<void> _sortData() async {
setState(() => _isSorting = true);
// 生成大量随机数据
final rawData = List.generate(1000000, (i) => (i * 7 + 13) % 1000000);
// 使用 compute 在子 Isolate 中排序
final sorted = await compute(sortLargeList, rawData);
setState(() {
_data = sorted.take(20).toList(); // 只展示前 20 条
_isSorting = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Isolate 排序')),
body: Column(
children: [
ElevatedButton(
onPressed: _isSorting ? null : _sortData,
child: _isSorting
? const CircularProgressIndicator()
: const Text('排序 100 万条数据'),
),
Expanded(
child: ListView.builder(
itemCount: _data.length,
itemBuilder: (context, index) {
return ListTile(title: Text('第 ${index + 1} 项: ${_data[index]}'));
},
),
),
],
),
);
}
}实战:JSON 解析不卡 UI
解析大型 JSON 文档是 Isolate 最常见的应用场景之一。
问题:主 Isolate 解析大 JSON 会卡 UI
// ❌ 不推荐:大 JSON 在主 Isolate 中解析,UI 会卡顿
Future<void> loadData() async {
final jsonString = await rootBundle.loadString('assets/large_data.json');
// 如果 JSON 有几 MB,这里会卡住 UI
final data = jsonDecode(jsonString);
setState(() => _items = data);
}解决:用 compute 解析
// ✅ 推荐:在子 Isolate 中解析,UI 不卡顿
// 顶层函数:在子 Isolate 中执行 JSON 解析
Map<String, dynamic> parseJson(String jsonString) {
return jsonDecode(jsonString);
}
Future<void> loadData() async {
final jsonString = await rootBundle.loadString('assets/large_data.json');
// 在子 Isolate 中解析
final data = await compute(parseJson, jsonString);
setState(() => _items = data);
}封装为通用工具
import 'dart:convert';
import 'package:flutter/foundation.dart';
/// 在 Isolate 中解析 JSON
Future<T> parseJsonInIsolate<T>(String jsonString, T Function(Map<String, dynamic>) fromJson) async {
final map = await compute(_parseJson, jsonString);
return fromJson(map);
}
// 顶层函数,供 compute 调用
Map<String, dynamic> _parseJson(String jsonString) {
return jsonDecode(jsonString);
}Isolate 的消息传递限制
Isolate 之间通过消息传递通信,但并非所有对象都能传递。
可以传递的类型
- 基本类型:
int、double、bool、String、null - 集合:
List、Map、Set(元素也必须是可传递类型) SendPort/ReceivePortTransferableTypedData(高效传递二进制数据)- 自定义对象(仅限简单对象,见下方说明)
不能传递的类型
BuildContext—— 绑定到主 Isolate 的 Widget 树Controller(如AnimationController、TextEditingController)Stream/StreamController- 包含闭包(函数)的对象
- 包含不可传递类型字段的对象
传递自定义对象
// ✅ 可以传递 —— 所有字段都是基本类型
class User {
final String name;
final int age;
User({required this.name, required this.age});
}
// ❌ 不能传递 —— 包含不可传递的类型
class BadExample {
final StreamController controller; // 不可传递
final void Function() callback; // 闭包不可传递
}注意
传递给 Isolate 的对象会被复制,而不是共享引用。子 Isolate 修改对象不会影响主 Isolate 中的原对象。
Isolate 线程池:Isolate.run
Dart 2.19 引入了 Isolate.run,它是 compute 的改进版本,使用系统管理的 Isolate 线程池,性能更好。
import 'dart:isolate';
Future<void> main() async {
final result = await Isolate.run(() {
// 可以直接写闭包
int sum = 0;
for (int i = 0; i < 100000000; i++) {
sum += i;
}
return sum;
});
print('结果: $result');
}Isolate.run vs compute
| 特性 | Isolate.run | compute |
|---|---|---|
| Dart 版本 | >= 2.19 | Flutter 所有版本 |
| 闭包支持 | 支持 | 不支持 |
| 线程池 | 系统管理 | 每次新建 Isolate |
| 性能 | 更好(复用 Isolate) | 稍差(每次创建销毁) |
推荐
如果你的项目 Dart SDK >= 2.19,优先使用 Isolate.run。它支持闭包,且使用线程池,性能更好。
完整实战示例:图片处理
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
// 图片处理参数
class ImageProcessParam {
final SendPort sendPort;
final List<int> imageBytes;
final double brightness;
ImageProcessParam({
required this.sendPort,
required this.imageBytes,
required this.brightness,
});
}
// 子 Isolate:调整亮度
void adjustBrightness(ImageProcessParam param) {
final codec = instantiateImageCodecFromBuffer(
param.imageBytes.buffer.asUint8List().buffer as ImmutableBuffer,
);
// 简化示例:实际项目中使用 image 包处理
final result = {'status': 'done', 'brightness': param.brightness};
param.sendPort.send(result);
}
class ImageProcessPage extends StatefulWidget {
const ImageProcessPage({super.key});
@override
State<ImageProcessPage> createState() => _ImageProcessPageState();
}
class _ImageProcessPageState extends State<ImageProcessPage> {
double _brightness = 1.0;
bool _isProcessing = false;
Future<void> _processImage() async {
setState(() => _isProcessing = true);
final receivePort = ReceivePort();
await Isolate.spawn(
adjustBrightness,
ImageProcessParam(
sendPort: receivePort.sendPort,
imageBytes: [], // 实际传入图片字节
brightness: _brightness,
),
);
receivePort.listen((message) {
setState(() => _isProcessing = false);
receivePort.close();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('处理完成')),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Isolate 图片处理')),
body: Column(
children: [
Text('亮度: ${_brightness.toStringAsFixed(1)}'),
Slider(
value: _brightness,
min: 0.0,
max: 2.0,
onChanged: _isProcessing ? null : (v) => setState(() => _brightness = v),
),
ElevatedButton(
onPressed: _isProcessing ? null : _processImage,
child: _isProcessing
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('处理图片'),
),
],
),
);
}
}常见问题
1. Isolate 和 async/await 有什么区别?
| 特性 | async/await | Isolate |
|---|---|---|
| 本质 | 单线程内的异步调度 | 多线程并发 |
| CPU 占用 | 等待时不占 CPU | 独占一个 CPU 核心 |
| 适用场景 | I/O 操作(网络、文件) | CPU 密集型计算 |
| UI 影响 | 不阻塞 UI | 不阻塞 UI |
// async/await —— 适合 I/O 操作
Future<void> fetchData() async {
final response = await http.get(Uri.parse('https://api.example.com/data'));
// 等待网络时,CPU 是空闲的,其他任务可以执行
}
// Isolate —— 适合 CPU 密集型操作
Future<void> calculate() async {
final result = await compute(heavyWork, data);
// 计算在另一个线程,主线程继续处理 UI
}2. 为什么 compute 的函数不能是实例方法?
compute 要求传入的函数必须是顶层函数或静态方法,因为实例方法隐式绑定了 this,而 this 指向的对象无法跨 Isolate 传递。
// ❌ 错误:实例方法
class MyWidget extends StatefulWidget {
int calculate(int n) { ... } // 隐式包含 this
}
// 在 compute 中使用
compute(this.calculate, 100); // 报错!
// ✅ 正确:顶层函数
int calculate(int n) {
int sum = 0;
for (int i = 0; i < n; i++) sum += i;
return sum;
}
compute(calculate, 100); // 正常3. 如何取消正在运行的 Isolate?
Future<void> runCancellableTask() async {
final receivePort = ReceivePort();
// spawn 返回 Isolate 对象
final isolate = await Isolate.spawn(
longRunningTask,
receivePort.sendPort,
);
// 设置超时自动取消
bool completed = false;
receivePort.listen((message) {
completed = true;
receivePort.close();
});
// 3 秒后如果没完成就杀掉 Isolate
await Future.delayed(const Duration(seconds: 3));
if (!completed) {
isolate.kill(priority: Isolate.immediate);
receivePort.close();
print('任务超时,已取消');
}
}方案选择指南
| 场景 | 推荐方案 |
|---|---|
| 一次性耗时计算 | compute 或 Isolate.run |
| JSON / 数据解析 | compute |
| 图片 / 音视频处理 | Isolate.spawn + 双向通信 |
| 需要进度回调的长时间任务 | Isolate.spawn + SendPort |
| 需要取消任务 | Isolate.spawn + isolate.kill() |
| I/O 操作(网络、文件读写) | async/await(不需要 Isolate) |
核心原则
- I/O 操作用 async/await —— 不占 CPU,不需要 Isolate
- CPU 密集型任务用 Isolate —— 避免 UI 卡顿
- 简单场景用 compute —— 代码最少,自动管理
- 复杂场景用 Isolate.spawn —— 双向通信、任务取消等
