为何Dart中exitPort处理器的if语句未在所有Isolate结束后执行?
问题描述
预期printTest方法在所有Isolate执行完成后才被调用,但实际它在Isolate未全部结束时就已执行,导致Closing the exitPort输出在中间而非末尾。程序通过if (--liveIsolates == 0)判断Isolate是否全部执行完毕,同时用Test类实例验证不同Isolate内的实例一致性。
源码
import "dart:isolate"; Test test = Test(); void main(final List<String> args) async { final exitPort = RawReceivePort(); const int numberOfIsolates = 1000; int liveIsolates = 0; exitPort.handler = (_) { print("Entering handler"); final alpha = "abcdefghijklmnopqurstuvwxyz"; print("$alpha $alpha $alpha $_"); if (--liveIsolates == 0) { printTest(); print("Closing the exitPort"); exitPort.close(); } print("Exiting handler"); }; liveIsolates = numberOfIsolates; for (int i = 0; i < numberOfIsolates; i++) { // 创建多个Isolate final isolate1 = await Isolate.spawn(foo, exitPort.sendPort, onExit: exitPort.sendPort); } print("Exiting main"); } void foo(final SendPort sendPort) { sendPort.send("Data to be send from foo"); print("Entering foo sendPort id ${sendPort.hashCode}"); test++; print("Exiting foo id test : ${test.hashCode}"); print("Exiting foo id test : $test"); } void printTest() { // 应由exitPort处理器最后调用 print("Value of test : $test"); } class Test { int _data = 0; void set(int value) => _data = value; int get data => _data; @override String toString() { return _data.toString(); } Test operator +(int _) { // 重载++运算符 _data++; return this; } }
核心原因:exitPort同时接收两类消息
你的exitPort被绑定了两种消息来源:
- Isolate主动调用
sendPort.send()发送的业务消息(来自foo函数里的sendPort.send("Data to be send from foo")) - Isolate退出时系统自动发送的
onExit通知消息(来自Isolate.spawn的onExit: exitPort.sendPort参数)
每一条消息都会触发exitPort.handler执行,而你在handler里对liveIsolates做了--liveIsolates操作——不管收到的是业务消息还是退出消息。
当创建1000个Isolate时,每个Isolate会发送2条消息:1条业务消息+1条退出消息,总共2000条消息。但你初始化liveIsolates = 1000,当第1000条消息(大概率是某个Isolate的业务消息)被处理时,--liveIsolates就会变成0,直接触发关闭逻辑,此时还有大量Isolate的消息没处理,自然会看到Closing the exitPort出现在输出中间。
额外说明:跨Isolate的全局变量无效
你用全局Test test实例验证Isolate实例一致性的逻辑不成立——Dart的Isolate是完全内存隔离的,每个Isolate会拥有独立的内存空间,全局变量test在每个Isolate里都是独立的副本,所以在foo里对test++的操作只会修改当前Isolate内的副本,主Isolate的test值始终是0,这也是printTest输出Value of test : 0的原因。
修复方案
要解决提前关闭的问题,需要区分业务消息和退出消息,比如用不同的Port分别处理:
import "dart:isolate"; Test test = Test(); void main(final List<String> args) async { final exitPort = RawReceivePort(); final dataPort = RawReceivePort(); const int numberOfIsolates = 1000; int liveIsolates = numberOfIsolates; // 只处理Isolate退出消息,才递减计数 exitPort.handler = (_) { print("Isolate exited"); if (--liveIsolates == 0) { printTest(); print("Closing ports"); exitPort.close(); dataPort.close(); } }; // 单独处理业务消息 dataPort.handler = (message) { print("Entering data handler"); final alpha = "abcdefghijklmnopqurstuvwxyz"; print("$alpha $alpha $alpha $message"); print("Exiting data handler"); }; for (int i = 0; i < numberOfIsolates; i++) { // 业务消息发往dataPort,退出通知发往exitPort await Isolate.spawn(foo, dataPort.sendPort, onExit: exitPort.sendPort); } print("Exiting main"); } void foo(final SendPort sendPort) { sendPort.send("Data to be send from foo"); print("Entering foo sendPort id ${sendPort.hashCode}"); Test localTest = Test(); // 每个Isolate的独立实例 localTest++; print("Exiting foo id test : ${localTest.hashCode}"); print("Exiting foo id test : $localTest"); } void printTest() { print("Value of main isolate test : $test"); } class Test { int _data = 0; void set(int value) => _data = value; int get data => _data; @override String toString() { return _data.toString(); } Test operator +(int _) { _data++; return this; } }
内容的提问来源于stack exchange,提问作者user8234870

