上一篇文章说到了ffi调用c函数,但是结尾留了一个问题就是c函数如果是一个延时操作,整个流程会调用多次,并且每一个c函数调用都使用了compute进行异步调用(这里不在业务层处理主要是想要业务层显得简洁,其实可以使用compute对整个业务进行异步),这个时候compute频繁创建isolate导致整个流程下来消耗时间过长,然后就有了一个想法,我能不能创建一个常驻的isolate,去执行我的c函数呢
我的思路是
1.熟悉isolate通信
其实最后看下来简单的理解就是两个(A,B)封闭的空间各干各的,中间有两个管道(Port),一个是从A->B,一个是从B->A
WechatIMG23.jpeg
A把要干的事通过管子给到B,B监听到后开始工作,结束之后把结果再通过管道给到A,基本的流程就是这样,撸代码
//特别需要注意:establishConn执行环境是rootIsolate
static void establishConn() async {
//第1步: 默认执行环境下是rootIsolate,所以创建的是一个rootIsolateReceivePort
ReceivePort rootIsolateReceivePort = ReceivePort();
//第2步: 获取rootIsolateSendPort
SendPort rootIsolateSendPort = rootIsolateReceivePort.sendPort;
//第3步: 创建一个newIsolate实例,并把rootIsolateSendPort作为参数传入到newIsolate中,为的是让newIsolate中持有rootIsolateSendPort, 这样在newIsolate中就能向rootIsolate发送消息了
newIsolate = await Isolate.spawn(createNewIsolateContext,
rootIsolateSendPort); //注意createNewIsolateContext这个函数执行环境就会变为newIsolate, rootIsolateSendPort就是createNewIsolateContext回调函数的参数
//第4步: 通过rootIsolateReceivePort接收到来自newIsolate的消息,所以可以注意到这里是await 因为是异步消息
rootIsolateReceivePort.listen((message) {
if (message is SendPort) {
print("newIsolateSendPort $message");
newIsolateSendPort = message;
}
});
}
//特别需要注意:createNewIsolateContext执行环境是newIsolate
static void createNewIsolateContext(SendPort rootIsolateSendPort) async {
//第1步: 注意callback这个函数执行环境就会变为newIsolate, 所以创建的是一个newIsolateReceivePort
ReceivePort newIsolateReceivePort = ReceivePort();
//第2步: 获取newIsolateSendPort, 有人可能疑问这里为啥不是直接让全局newIsolateSendPort赋值,注意这里执行环境不是rootIsolate
SendPort newIsolateSendPort = newIsolateReceivePort.sendPort;
//第3步: 特别需要注意这里,这里是利用rootIsolateSendPort向rootIsolate发送消息,只不过发送消息是newIsolate的SendPort, 这样rootIsolate就能拿到newIsolate的SendPort
rootIsolateSendPort.send(newIsolateSendPort);
newIsolateReceivePort.listen((message) {
});
}
这一步结束,我们常驻isolate已经有了,并且它和我们mainIsolate已经建立起了通信通道
2.如何使用这个常驻isolate
首先我想要什么?
我想实现的就是,给你个函数地址,给你参数,你去干活吧,然后结束之后返回给我结果,伪代码是这样的
doWork(Function work,params) async {
newIsolateSendPort?.send([work,params]);
...(work执行(耗时))
return work执行结果
}
但是我的回调结果在listen里边啊,我怎么做才能让doWork这个函数等我呢?最后在朋友的提示下,看到了Completer,这个问题就迎刃而解了,这个类可以让你的函数停止,然后调用complete,才会继续执行,这不是正是我想要的嘛?等着,然后listen到结果,执行complete,让doWork函数继续进行?perfect!!!
然后就是一些进一步的优化,
将通信内容(函数地址,参数,id)对象化,
///通知子isolate工作
class SendMessage {
int id;
Map<String,dynamic> params;
Future Function(Map<String,dynamic> params) work;
SendMessage({
required this.id,
required this.params,
required this.work,
});
}
///通知main isolate,活干完了,给你结果
class ReceiveMessage {
int id;
dynamic result;
ReceiveMessage({
required this.id,
this.result,
});
}
将常驻isolate表达成单例,最后就是最终的代码
import 'dart:async';
import 'dart:isolate';
class GlobalIsolate {
static Isolate? _newIsolate;
static SendPort? _newIsolateSendPort;
static int _i = 0;
static final Map<int, dynamic> _works = {};
static Future isolateDo({
required Future Function(Map<String,dynamic> params) work,
required Map<String,dynamic> params,
}) async {
if (_newIsolate == null) {
await _initNewIsolate();
}
SendMessage message = SendMessage(id: _i, params: params, work: work);
_newIsolateSendPort?.send(message);
Completer c = Completer();
_works[_i] = c;
_i++;
try {
final result = await c.future;
return result;
} catch (e) {
return null;
}
}
static Future _initNewIsolate() async {
if (_newIsolate != null) return;
//第1步: 默认执行环境下是rootIsolate,所以创建的是一个rootIsolateReceivePort
ReceivePort rootIsolateReceivePort = ReceivePort();
//第2步: 获取rootIsolateSendPort
SendPort rootIsolateSendPort = rootIsolateReceivePort.sendPort;
//第3步: 创建一个newIsolate实例,并把rootIsolateSendPort作为参数传入到newIsolate中,为的是让newIsolate中持有rootIsolateSendPort, 这样在newIsolate中就能向rootIsolate发送消息了
_newIsolate = await Isolate.spawn(_createNewIsolateContext,
rootIsolateSendPort); //注意createNewIsolateContext这个函数执行环境就会变为newIsolate, rootIsolateSendPort就是createNewIsolateContext回调函数的参数
//第7步: 通过rootIsolateReceivePort接收到来自newIsolate的消息,所以可以注意到这里是await 因为是异步消息
//只不过这个接收到的消息是newIsolateSendPort, 最后赋值给全局newIsolateSendPort,这样rootIsolate就持有newIsolate的SendPort
// var message = await rootIsolateReceivePort.first;
//第8步,建立连接成功
// print(messageList[0] as String);
// newIsolateSendPort = message as SendPort;
// print("newIsolateSendPort $message");
rootIsolateReceivePort.listen((message) {
if (message is SendPort) {
print("newIsolateSendPort $message");
_newIsolateSendPort = message;
} else if (message is ReceiveMessage) {
if (_works[message.id] is Completer) {
Completer c = _works[message.id];
c.complete(message.result);
_works.remove(message.id);
}
}
});
}
//特别需要注意:createNewIsolateContext执行环境是newIsolate
static void _createNewIsolateContext(SendPort rootIsolateSendPort) async {
//第4步: 注意callback这个函数执行环境就会变为newIsolate, 所以创建的是一个newIsolateReceivePort
ReceivePort newIsolateReceivePort = ReceivePort();
//第5步: 获取newIsolateSendPort, 有人可能疑问这里为啥不是直接让全局newIsolateSendPort赋值,注意这里执行环境不是rootIsolate
SendPort newIsolateSendPort = newIsolateReceivePort.sendPort;
//第6步: 特别需要注意这里,这里是利用rootIsolateSendPort向rootIsolate发送消息,只不过发送消息是newIsolate的SendPort, 这样rootIsolate就能拿到newIsolate的SendPort
rootIsolateSendPort.send(newIsolateSendPort);
newIsolateReceivePort.listen((message) {
if (message is SendMessage) {
message.work(message.params).then((value) => rootIsolateSendPort
.send(ReceiveMessage(id: message.id, result: value)));
}
});
}
}
///通知子isolate工作
class SendMessage {
int id;
Map<String,dynamic> params;
Future Function(Map<String,dynamic> params) work;
SendMessage({
required this.id,
required this.params,
required this.work,
});
}
///通知main isolate,活干完了,给你结果
class ReceiveMessage {
int id;
dynamic result;
ReceiveMessage({
required this.id,
this.result,
});
}
最后测试一下
test() async {
await GlobalIsolate.init();
int a = 10;
final result = await GlobalIsolate.isolateDo(
params: {"a":a},
work: (Map<String,dynamic> params) async {
sleep(Duration(seconds: 2));
var a= params["a"];
return {
"result1":"1 - ${a++}",
"result2":"2 - ${a++}",
"result3":"3 - ${a++}",
};
}
);
print("result = $result");
}
flutter: newIsolateSendPort SendPort
flutter: result = {result1: 1 - 10, result2: 2 - 11, result3: 3 - 12}
网友评论