dart 虽然是一个单线程语言 但是不代表他不支持多线程并发
- 在dart中线程不叫线程叫做isolate(隔离区)所有的代码都运行在这
- 类似于线程但不共享内存的独立工作程序,仅通过消息进行通信。
- 每个isolate 都有一个完整的事件循环机制,每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问。
- 这意味着在一个 Isolate 中运行的代码与另外一个 Isolate不存在任何关联。
- 依赖这点所以我们通过Isolate实现并发
mainIsolate
代表调用Isolate或者叫主Isolate
newIsolate
代表**新创建的Isolate
**mainReceivePort
代表**调用Isolate
的ReceivePort
**newReceivePort
代表**新Isolate
的ReceivePort
**mainSendPort
代表**调用Isolate
的SendPort
**newSendPort
代表**新Isolate
的SendPort
**在
mainIsolate
中创建newIsolate
有两种方法spawn
,spawnUri
创建步骤
- 创建
newIsolate
并与当前调用mainIsolate
并通过sendPort
建立连接
- 因为
Isolate
之间不共享内存且通过消息交互,所以**Isolate
之间通讯需要持有对方发送消息的端口,文档里叫**SendPort
- 对于
sendPort
文档里这样描述:[SendPort]s are created from [ReceivePort]s.- 翻译:发送端口是从接收端口创建的
RecivePort
文档里这样描述:Together with SendPort, the only means of communication between isolates.- 与发送端一起,是一种
isolate
之间的通讯方式- 所以
Isolate
之间通讯需要持有对方的SendPort
- 创建
newIsolate
- 获得
mainIsolate
的mainRecivePort
和mainSendPort
- 要将
mainIsolate
的mainSendPort
发送到newIsolate
mainRecivePort
监听mainSendPort
发送回来的数据- 至于如何将
mainSendPort
发送到newIsolate
的请看下面的例子- 发送数据
- 通过各自的sendPort.send
- 接收数据通过
- 各自的RecivePort
- 在合适的机会销毁Isolate
下面看具体的步骤
spawn创建并生成与当前Isolate共享相同代码的Isolate。
Future
entryPoint
entryPoint
指定要在派生的隔离中调用的初始函数,message
只有[message]作为entryPoint
的参数。
message
通常用于传送调用Isolate
的SendPort
对象entryPoint
函数就是要在**newIsolate
**中运行的函数sendport
mainIsolate
的mainSendPort
** 作为**message
传递到将要在newIsolate
** 中执行的入口函数(entryPoint
)中,使 newIsolate
持有mainSendPort
newIsolate
** 中执行,我们再通过**mainSendPort
** 将**NewIsolate
的newSendPort
** 发送到**mainIsolate
**中 ,使 mainIsolate
持有newSendPort
mainIsolate
持有了newSendPort
** ,
mainIsolate
通过newSendPort
** 可以将**mainIsolate
中的消息发送到newIsolate
**newIsolate
持有了mainSendPort
**
newIsolate
通过mainSendPort
** 可以将**newIsolate
中的消息发送到mainIsolate
**主Isolate的SendPort
发送到Newisolate
**entryPoint
)」必须是顶级函数或静态方法。示例代码
import 'dart:io';
import 'dart:isolate';
main() async {
print("main start");
createIsolate();
print("main end");
}
Isolate newIsolate;
void createIsolate() async {
// 两个Isolate要想互相通讯须持有对方的的sendPort
// 获取mainIsolate的监听器 mainReceivePort
ReceivePort main_rp = ReceivePort();
// 获取 mainIsolate 的 SendPort 并作为参数传递给newIsolate
// 使 newIsolate 持有 mainSendPort,用于通讯
// 使 newIsolate 可以通过 mainSendPort 将 newIsolate 的发送消息回 mainIsolate
SendPort main_send = main_rp.sendPort;
// 创建新的isolate
newIsolate = await Isolate.spawn(excuter, main_send);
// 这里需要得到 newIsolate 的 SendPort,
// 让 mainIsolate 持有 newSendPort,用于通讯
// 使 mainIsolate 可以通过 newSendPort 将 mainIsolate 的发送消息回 newIsolate
// 注意 这里 newSendPort 是 newIsolate中的mainSendPort 发送回来的所以要在监听中获取newSendPort
SendPort new_send;
//主接收器(mainReceivePort)开始监听newIsolate中的mainSendPort发送回来的消息
main_rp.listen((message) {
print("NewIsolat通过main_send发送来一条消息 $message ,到主Isolate");
if (message[0] == 0) {
// 获取newSendPort
new_send = message[1] as SendPort;
} else {
new_send?.send("mian_isolate 通过new_send发送了一条消息到NewIsolate");
}
});
}
// 入口函数将在newIsolate中执行
void excuter(SendPort mainSendPort) {
// 获取newIsolate的监听器newReceivePort
ReceivePort new_rp = ReceivePort();
//newReceivePort开始监听 mainIsolate中的newSendPort发送回来的消息
new_rp.listen((message) {
print(message);
// 接收到第一条main发送过来的函数 就销毁newIsolate
print("销毁NewIsolate");
destroyNewIsolate();
});
// 获取newIsolate的 SendPort
SendPort new_send = new_rp.sendPort;
//将其发送到 mainIsolate
// 让 mainIsolate 持有 newSendPort,用于通讯
// 使 mainIsolate 可以通过 newSendPort 将 mainIsolate 的发送消息回 newIsolate
mainSendPort.send([0, new_send]);
// 模拟耗时5秒
sleep(Duration(seconds: 5));
mainSendPort.send([1, "excuter 任务完成"]);
print("NewIsolat 执行结束");
}
//销毁newIsolate
destroyNewIsolate() {
// 任务执行结束销毁newIsolate
newIsolate?.kill(priority: Isolate.immediate);
newIsolate = null;
}
/* 输出
newIsolat通过main_send发送来一条消息 [0, SendPort] ,到mainIsolate
newIsolat通过main_send发送来一条消息 [1, excuter 任务完成] ,到mainIsolate
NewIsolat 执行结束
mianIsolate 通过new_send发送了一条消息到newIsolate
销毁NewIsolate
*/
创建并派生一个Isolate,该Isolate使用指定的URI从库中运行代码。
Isolate.spawnUri(Uri uri,List
;`
spawnUri
方法有三个必须的参数,
SendPort
注意这种方式
newIsolate
代码在一个单独的文件里
newIsolate
的的执行函数,必须包是一个main函数,它是
newIsolate的入口方法,
main
函数必须可以用零个一个或者两个参数调用
`main()`
`main(args)`
`main(args, message)`
该main
函数中的args
参数列表,正对应spawnUri
中的第二个参数。如不需要向newIsolate
中传参数,该参数可传空List
message则是调用Isolate的SendPort
示例代码
mainIsolate
import 'dart:isolate';
Isolate newIsolate;
main() async {
ReceivePort mainReceivePort = ReceivePort();
SendPort mainSendPort = mainReceivePort.sendPort;
List list = ["hello, isolate", "this is args"];
var uri = Uri(path: "./newTaskUri.dart");
// 创建newIsolate 并建立连接
newIsolate = await Isolate.spawnUri(uri, list, mainSendPort);
// 需要获取 newSendPort 用于通讯
// newSendPort 是 newIsolate中的mainSendPort 发送回来的所以要在监听中获取结果
SendPort newSendPort;
mainReceivePort.listen((message) {
print("newIsolat通过main_send发送来一条消息 $message ,到mainIsolate");
if ("excuter 任务完成" == message[1]) {
// 销毁newIsolate
print("销毁newIsolate");
destroyNewIsolate();
}
if (message[0] == 0) {
// 获取newSendPort
newSendPort = message[1] as SendPort;
} else {
newSendPort?.send("mian_isolate 通过new_send发送了一条消息到newIsolate");
}
});
}
//销毁newIsolate
destroyNewIsolate() {
// 任务执行结束销毁newIsolate
newIsolate?.kill(priority: Isolate.immediate);
newIsolate = null;
}
newIsolate
文件newTaskUri.dart
import 'dart:io';
import 'dart:isolate';
// 这里的main 就是入口函数 在newIsolate中执行
// 就相当与 spawn中的 excuter
// 内部执行回传sendport,消息监听发送,逻辑是一样的
// 区别就是多了一个参数列表可以传一些参数处理些逻辑 功能更丰富了
void main(args, SendPort mainSendPort) {
try {
print("newIsolate 开始");
print("newIsolate (参数列表)args: $args");
ReceivePort newRecivePort = new ReceivePort();
//newReceivePort开始监听 newSendPort发送回来的消息
newRecivePort.listen((message) {
print(message);
// 接收到第一条消息
});
// 获取newSendPort 并通过mainSendPort 回传到mainIsolate
SendPort newSendPort = newRecivePort.sendPort;
mainSendPort.send([0, newSendPort]);
// 模拟耗时5秒
sleep(Duration(seconds: 5));
mainSendPort.send([1, "excuter 任务完成"]);
print("NewIsolat 执行结束");
} catch (e) {
print("myerr $e");
}
}
输出结果
/**
newIsolate 开始
newIsolate (参数列表)args: [hello, isolate, this is args]
newIsolat通过main_send发送来一条消息 [0, SendPort] ,到mainIsolate
NewIsolat 执行结束
newIsolat通过main_send发送来一条消息 [1, excuter 任务完成] ,到mainIsolate
销毁newIsolate
*/
与spawn
输出结果对比少了一个mainIsolat
向newIsolate
发送消息,是因为代码中销毁newIsolate
时机不同
spawn
在newIsolate
执行结束后mainIsolate
向newIsolate
发送消息且被处理后销毁newIsolate
spawnUri
在newIsolate
执行结束后就销毁了newIsolate
无论是上面的**spawn
还是spawnUri
**,运行后都会创建两个Isolate
想要相互通讯就必须持有对方的SendPort
newIsoalte
要持有mainSendPort
靠创建Isolate
是入口点函数传参 参数即`mainSendPort``要持有
newSendPort靠传入
newIsolate的
mainSendPort发送到
mainIsolate`释放newIsolate
kill
将Isolate杀死,否则Isolate 会一直存在造成内存消耗Platform-Channel 通信仅仅由主 isolate 支持。该主 isolate 对应于应用启动时创建的 isolate。
区别
swpanUri
的newIsolate
必须在单独的文件里,又因为必须有main函数作为入口,所以程序会出现两个main
在flutter环境下一直又一个main的错误未处理 我猜测可能是因为两个main函数引起的,也有可能是上面第四点原因导致的如果有人知道请评论告诉我下 谢谢了
spawn
通常我们newIsolate
和mainIsolate
写在同一个文件,也不会出现两个main函数方便管理Isolate
在flutter环境下spwan
创建可以正常执行
spwan
要注意入口点函数必须是顶层函数或者静态函数可以看到 无论是实现上述哪一种isolate 代码数量都是比较繁琐的
对此Flutter提供了一个函数**
compute
** 该函数封装了通过spawn
实现Isolate
的代码避免我们去写通过
spawn
创建Isolate的一系列代码,直接通过compute函数,这样让我们的代码看起来更简洁了
compute
如果任务只是进行一次计算返回结果,不需要双端多次沟通的话 使用compute 函数将非常简单
compute
(ComputeCallback
callback, Q message)
compute函数有两个必须的参数
示例
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'ComputeIsolate',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: Text("ComputeIsolate")),
body: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: FlatButton(
onPressed: () async {
String s = await compute(work, 4);
print(s);
},
child: Text(
"Click",
)),
),
Center(
child: Text(
"Click",
)),
])));
}
}
String work(num duration) {
print("work start");
sleep(Duration(seconds: duration));
return "$duration 秒后执行结束";
}
// 运行点击屏幕 后输出
// work start
// 4 秒后执行结束
怎么样 是不是很简单
掘金文章 [译] Flutter 异步编程:Future、Isolate 和事件循环