前面说过 Flutter 实现异步的方式有:async、awite
、Futrue
,但是这些本质还是 handle 队列那套,更何况消息队列还是跑在 UI 线程里的,要是你真有什么耗时的操作放在这里妥妥的光剩卡了。所以该开新线程还是得开,这里我们来说说怎么 new Isolate 和 Isolate 之间的通讯,但后再来看看系统给我们提供的简便函数:compute
Isolate 的创建和 Isolate 之间的通讯
上文书 Isolate
之间是内存隔离的,单个 Isolate
内部是是以消息队列的方式执行的,这是 Isolate
的特征。语言之间概念基本趋同,只是实现和细微不同,但是因概相同自然就会诞生相同的需求,Dart 自然提供了 Isolate
之间通讯的方式:port
端口,可以很方便的实现 Isolate
之间的双向通讯,原理是向对方的队列里写入任务
port
成对出现,分为:receivePort 接受端口
和 SendPort 发送端口
,receivePort
可以自己生成对应的 SendPort
,只要把 SendPort
传递给对方就能实现从 SendPort
->receivePort
的通许,当然这是单项的,双向通讯的其实也一样,对面把自己的 SendPort
传过来就成了
var receivePort = ReceivePort();
var sendPort = receivePort.sendPort;
创建 Isolate
的 API 是:await Isolate.spawn(Function,SendPort)
,因为这是个异步操作,所以加上 await
,Function
这是个方法,是新的线程执行的核心方法,和 run
方法一样的意思,SendPort
就是我们要传给对面的通讯器
var anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);
Isolate.listen
监听方法我们可以拿到这个 Isolate
传递过来的数据,Isolate
之间什么数据类型都可以传递,不必做任何标记,肯定是底层帮我们实现好了,很省事
receivePort.listen((date) {
print("Isolate 1 接受消息:data = $date");
});
await receivePort.first
可以等待获取第一个返回结果,但是不能和 receivePort.listen
一起写,有冲突,只能选择一个
final sendPort = await receivePort.first as SendPort;
Isolate 关闭很直接,在 main isolate 中对其控制的 Isolate 调用 kill
方法就行了
void stop(){
print("kill isolate");
isolate?.kill(priority: Isolate.immediate);
isolate =null;
}
不废话了,上面很简单的,把原理一说大家都明白,下面直接看代码:
Isolate 单向通讯
isolate 代码:
import 'dart:isolate';
var anotherIsolate;
var value = "Now Thread!";
void startOtherIsolate() async {
var receivePort = ReceivePort();
anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);
receivePort.listen((date) {
print("Isolate 1 接受消息:data = $date,value = $value");
});
}
void otherIsolateInit(SendPort sendPort) async {
value = "Other Thread!";
sendPort.send("BB");
}
运行代码:
import 'DartLib.dart';
void main(){
startOtherIsolate();
}
结果:
Isolate 1 接受消息:data = BB,value = Now Thread!
Isolate 双向通讯
isolate 代码:
import 'dart:isolate';
var anotherIsolate;
var value = "Now Thread!";
void startOtherIsolate() async {
var receivePort = ReceivePort();
var sendPort;
anotherIsolate = await Isolate.spawn(otherIsolateInit, receivePort.sendPort);
receivePort.listen((date) {
if (date is SendPort) {
sendPort = date as SendPort;
print("双向通讯建立成功");
return;
}
print("Isolate 1 接受消息:data = $date");
sendPort.send("AA");
});
}
void otherIsolateInit(SendPort sendPort) async {
value = "Other Thread!";
var receivePort = ReceivePort();
print("Isolate 2 接受到来自 Isolate 1的port,尝试建立同 Isolate 1的双向通讯");
receivePort.listen((date) {
print("Isolate 2 接受消息:data = $date");
});
sendPort.send(receivePort.sendPort);
for (var index = 0; index < 10; index++) {
sendPort.send("BB$index");
}
}
运行代码:
import 'DartLib.dart';
void main(){
startOtherIsolate();
}
运行结果:
Isolate 2 接受到来自 Isolate 1的port,尝试建立同 Isolate 1的双向通讯
双向通讯建立成功
Isolate 1 接受消息:data = BB0
Isolate 1 接受消息:data = BB1
Isolate 1 接受消息:data = BB2
Isolate 1 接受消息:data = BB3
Isolate 1 接受消息:data = BB4
Isolate 1 接受消息:data = BB5
Isolate 1 接受消息:data = BB6
Isolate 1 接受消息:data = BB7
Isolate 1 接受消息:data = BB8
Isolate 1 接受消息:data = BB9
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
Isolate 2 接受消息:data = AA
系统 API:computer 函数
上面我们自己 new 一个 Isoalte
并实现通讯,多少有点麻烦,从封装的角度看其中代码基本是重复的,所以 Google 就提供了一个 API 来干这事:compute
方法
compute
方法是 Flutter
提供给我们的(记住不是 Dart),compute
内部会创建一个 Isolate
并返回计算结果,体验上和一次性线程一样,性能多少有些浪费,但是也有使用范围
compute(function,value)
compute 函数接受2个参数,第一个就是新线程的核心执行方法,第二个是传递过新线程的参数,可以是任何类型的数据,几个也可以,但是要注意,function 函数的参数设计要和 value 匹配
compute
方法在 import 'package:flutter/foundation.dart'
这个包里面
看个例子:
import 'dart:io';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
void newTask() async {
print("开始耗时计算,当前 isolate = ${Isolate.current.toString()}");
var result = await compute(getName, "name");
print(result);
}
String getName(String name) {
print("正在获取结果中...,当前 isolate = ${Isolate.current.toString()}");
sleep(Duration(seconds: 2));
return "Name";
}
运行代码:
newTask();
I/flutter (24384): 开始耗时计算,当前 isolate = Instance of 'Isolate'
I/flutter (24384): 正在获取结果中...,当前 isolate = Instance of 'Isolate'
I/flutter (24384): Name
compute 函数源码
compute 的源码不难,稍微用些新就能看懂,就是 new 了一个 isolate 出来,awite 第一个数据然后返回
//compute函数 必选参数两个,已经讲过了
Future<R> compute<Q, R>(ComputeCallback<Q, R> callback, Q message, { String debugLabel }) async {
//如果是在profile模式下,debugLabel为空的话,就取callback.toString()
profile(() { debugLabel ??= callback.toString(); });
final Flow flow = Flow.begin();
Timeline.startSync('$debugLabel: start', flow: flow);
final ReceivePort resultPort = ReceivePort();
Timeline.finishSync();
//创建isolate,这个和前面讲的创建isolate的方法一致
//还有一个,这里传过去的参数是用_IsolateConfiguration封装的类
final Isolate isolate = await Isolate.spawn<_IsolateConfiguration<Q, R>>(
_spawn,
_IsolateConfiguration<Q, R>(
callback,
message,
resultPort.sendPort,
debugLabel,
flow.id,
),
errorsAreFatal: true,
onExit: resultPort.sendPort,
);
final R result = await resultPort.first;
Timeline.startSync('$debugLabel: end', flow: Flow.end(flow.id));
resultPort.close();
isolate.kill();
Timeline.finishSync();
return result;
}
Isolate 使用场景
Isolate 虽好,但也有合适的使用场景,不建议滥用 Isolate,应尽可能多的使用Dart中的事件循环机制去处理异步任务,这样才能更好的发挥Dart语言的优势
那么应该在什么时候使用Future,什么时候使用Isolate呢?一个最简单的判断方法是根据某些任务的平均时间来选择:
- 方法执行在几毫秒或十几毫秒左右的,应使用Future
- 如果一个任务需要几百毫秒或之上的,则建议创建单独的Isolate
除此之外,还有一些可以参考的场景
- JSON 解码
- 加密
- 图像处理:比如剪裁
- 网络请求:加载资源、图片