什么是isolate
dart 虽然是一个单线程语言 但是不代表他不支持多线程并发
- 在dart中线程不叫线程叫做isolate(隔离区)所有的代码都运行在这
- 类似于线程但不共享内存的独立工作程序,仅通过消息进行通信。
- 每个isolate 都有一个完整的事件循环机制,每个隔离区都有自己的内存堆,确保每个隔离区的状态都不会被其他隔离区访问。
- 这意味着在一个 Isolate 中运行的代码与另外一个 Isolate不存在任何关联。
- 依赖这点所以我们通过Isolate实现并发
名词解释
- 在下面描述中
mainIsolate
代表调用Isolate或者叫主Isolate
newIsolate
代表**新创建的Isolate
**mainReceivePort
代表**调用Isolate
的ReceivePort
**newReceivePort
代表**新Isolate
的ReceivePort
**mainSendPort
代表**调用Isolate
的SendPort
**newSendPort
代表**新Isolate
的SendPort
**
实现Isolate
在
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
-
spawn创建并生成与当前Isolate共享相同代码的Isolate。
-
Future<Isolate> spawn<T>(void entryPoint(T message), T message)
- 可以看到每一个isolate 需要两个必要参数
- 入口点函数
entryPoint
- 文档对其这样描述
- 参数
entryPoint
指定要在派生的隔离中调用的初始函数, - 入口点函数在新的Isolate中被调用,
message
只有[message]作为entryPoint
的参数。message
通常用于传送调用Isolate
的SendPort
对象
- 入口点函数
- 即
entryPoint
函数就是要在**newIsolate
**中运行的函数 - 因为Isolate相互通讯需要持有对方的
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
**
- 所以我们将**
- 需要注意 isolate 的「入口函数(
entryPoint
)」必须是顶级函数或静态方法。
- 可以看到每一个isolate 需要两个必要参数
-
示例代码
-
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 */
spawn Uri
-
创建并派生一个Isolate,该Isolate使用指定的URI从库中运行代码。
-
Isolate.spawnUri(Uri uri,List<String> args,var message)
;` -
spawnUri
方法有三个必须的参数,- 第一个是Uri,指定一个新Isolate代码文件的路径,
- 第二个是参数列表,类型是List,
- 第三个是消息。其实是发送消息的端口
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<String> 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- spawn创建并生成与当前Isolate共享相同代码的新Isolate。
- spawnUri 一个独立的不与调用Isolate共享代码的新Isolate
-
想要相互通讯就必须持有对方的
SendPort
newIsoalte
要持有mainSendPort
靠创建Isolate
是入口点函数传参 参数即`mainSendPort``- ``mainIsolate
要持有
newSendPort靠传入
newIsolate的
mainSendPort发送到
mainIsolate`
-
释放
newIsolate
- 当我们使用完自己创建的Isolate之后,最好调用
kill
将Isolate杀死,否则Isolate 会一直存在造成内存消耗
- 当我们使用完自己创建的Isolate之后,最好调用
-
Platform-Channel 通信仅仅由主 isolate 支持。该主 isolate 对应于应用启动时创建的 isolate。
- 也就是说,通过编程创建的 isolate 实例,无法实现 Platform-Channel 通信 这难道是
区别
-
swpanUri
的newIsolate
必须在单独的文件里,又因为必须有main函数作为入口,所以程序会出现两个main -
在flutter环境下一直又一个main的错误未处理 我猜测可能是因为两个main函数引起的,也有可能是上面第四点原因导致的如果有人知道请评论告诉我下 谢谢了
-
spawn
通常我们newIsolate
和mainIsolate
写在同一个文件,也不会出现两个main函数方便管理Isolate -
在flutter环境下
spwan
创建可以正常执行spwan
要注意入口点函数必须是顶层函数或者静态函数
在flutter中创建Isolate
可以看到 无论是实现上述哪一种isolate 代码数量都是比较繁琐的
对此Flutter提供了一个函数**
compute
** 该函数封装了通过spawn
实现Isolate
的代码避免我们去写通过
spawn
创建Isolate的一系列代码,直接通过compute函数,这样让我们的代码看起来更简洁了
compute
如果任务只是进行一次计算返回结果,不需要双端多次沟通的话 使用compute 函数将非常简单
compute<Q, R>(ComputeCallback<Q, R> callback, Q message)
-
compute函数有两个必须的参数
- callback 为执行函数
- 必须是顶级函数或者静态方法
- message为消息 可以是callback执行函数的参数
- callback 为执行函数
-
示例
-
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: <Widget>[ 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 秒后执行结束
-
-
怎么样 是不是很简单
我们应该什么时候使用Future和Isolate
- 耗时较多的任务放到Isolate中
- 如果你不想你的Ui卡顿或者程序中断
- 通过之前的时间循环机制我们了解到Future只是将任务放到了Event队列,还是在当前Isolate 不过是等其他代码执行结束在执行Event队列中的任务,而UI渲染交互等又都是在Event队列中处理,如果我们Future任务耗时过多会导致Ui卡顿甚至整个进程都被中断,所以我们才需要多Isolate 并发处理任务
- 直观的说可以根据任务执行时间长短来区分
- 代码段运行时间只要几十毫秒=>Future
- 代码段运行时间要几百毫秒甚至更久应该用Isolate
- 比如Io操作
- 如果你不想你的Ui卡顿或者程序中断
总结
- Isolate虽然可以并发但是也要考虑适用场景
- 如果需要使用Isolate 要考虑场景
- 只执行一次返回结果 不需要多次通讯 使用compute函数
- 如果需要多次沟通我们可以通过 spawn 来创建Isolate