Links

20.Dart异步编程:Isolates和事件循环

前言

Dart 是单线程语言,它为 Futures Steams 后台作业,以及编写现代化、异步、响应式的 Flutter 应用程序所需的其他内容提供支持。
1

Isolate

Isolate 是所有 Dart 代码运行的地方。它就像机器中的一个小空间,它拥有自己的内存块和运行事件循环单一线程
在其他语言中,比如 C++ 你可以让多个线程共享相同的内存。并运行他们想要的代码。但是在 Dart 中,每个线程都有自己的 Isolate 和它自己的内存,它只处理事件。
许多 Dart 应用程序,在单一 Isolate 中运行所有代码。但是如果需要,你可以使用多个 Isolate。
2
如果你要运行的计算量太过庞大,如果它在主要的 Isolate 中运行的话,会导致应用程序扔掉当前的栈帧(Drop Frame)

子Isolate

你可以使用 Isolate.spawn 或者 Flutter 的计算函数,两者都会创建一个单独的 Isolate 来进行运算。让你主要的 Isolate 可以不受影响。
3
新的 Isolate 将会获得独立的事件循环和自己的内存,即使原本的 Isolate 是这个新 Isolate 的母体,原本的 Isolate 也不可以访问它。
4
这就是 Isolate 名称的来源。这些小空间之间彼此隔离。

Isolate 间通讯

Isolate 间合作的唯一方式,是来回传递消息。
一个 Isolate 会给另一个 Isolate 发送消息,接收消息的 Isolate 会使用他的事件循环处理消息。
5

Isolate 为什么这样设计

缺乏共享内存,可能听起来有些严格。 特别如果你是使用 Java C++ 这样的语言的话。但它对 Dart 编译器有一些关键性的好处。
比如:
  • Isolate 的内存分配和垃圾回收不需要
  • 因为他只有单一线程,因此如果它不忙,你就知道内存没有发生变化。这对 Flutter 应用程序非常有用。它有时需要快速构建和拆解很多 Widget。

事件循环

6
想象一下,应用程序的生命,在时间轴中延伸。期间会有很多外部信息的输入,而我们并不能预知这些信息什么时候会输入。这时我们就需要一个永不阻塞的线程来处理所有事件,它运行一个事件循环。
7
他从事件队列中,取出最旧的事件,处理它,返回,再取出下一个事件处理,依次循环,直到队列清空。
当事件清空,事件循环就会进入等待,直到有新的事件加入队列。
所有高级API都可用于异步编程。他们全部建立在这个简单的循环上。

Isolate 的使用

真多线程

void main() {
print('${DateTime.now()}: 1');
Isolate.spawn(isolateFunc, 15);
sleep(Duration(seconds: 3));
print('${DateTime.now()}: Sleep end');
}
void isolateFunc(int value) {
sleep(Duration(seconds: 1));
print('${DateTime.now()}: isolateFunc end $value');
}
通过日志我们发现:
flutter: 2021-11-28 10:12:09.313570: 1
flutter: 2021-11-28 10:12:10.422793: isolateFunc end 15
flutter: 2021-11-28 10:12:12.349747: Sleep end
sleep 的3秒并没有影响到 Isolate 调用,这里真正实现了多线程。

资源竞争

在进行 iOS 多线程开发的时候,我们会遇到资源竞争的问题。我们通常通过加锁来保证逻辑的正常。那么在使用 Isolate 的时候会不会也遇到这样的情况呢?
int number = 1;
void main() {
print('${DateTime.now()}: 1');
Isolate.spawn(isolateFunc, 1);
Isolate.spawn(isolateFunc, 2);
Isolate.spawn(isolateFunc, 3);
Isolate.spawn(isolateFunc, 4);
Isolate.spawn(isolateFunc, 5);
Isolate.spawn(isolateFunc, 6);
Isolate.spawn(isolateFunc, 7);
Isolate.spawn(isolateFunc, 8);
Isolate.spawn(isolateFunc, 9);
Isolate.spawn(isolateFunc, 10);
sleep(Duration(seconds: 5));
print('${DateTime.now()}: Sleep end,number = $number');
}
void isolateFunc(int value) {
number += 1;
print('${DateTime.now()}: 第$value个调用, number = $number');
}
按照 iOS 开发经验理解,上面的输出应该是乱序的,而且 number 的读写也是会比较乱的。且 sleep 5秒后输出的 number 值会 +10。
我们看下输出:
flutter: 2021-11-28 10:24:18.275046: 1
flutter: 2021-11-28 10:24:18.506326: 第1个调用, number = 2
flutter: 2021-11-28 10:24:18.543709: 第3个调用, number = 2
flutter: 2021-11-28 10:24:18.572635: 第4个调用, number = 2
flutter: 2021-11-28 10:24:18.589551: 第5个调用, number = 2
flutter: 2021-11-28 10:24:18.613625: 第7个调用, number = 2
flutter: 2021-11-28 10:24:18.613557: 第8个调用, number = 2
flutter: 2021-11-28 10:24:18.618459: 第2个调用, number = 2
flutter: 2021-11-28 10:24:18.645893: 第6个调用, number = 2
flutter: 2021-11-28 10:24:18.669942: 第10个调用, number = 2
flutter: 2021-11-28 10:24:18.674398: 第9个调用, number = 2
flutter: 2021-11-28 10:24:23.296321: Sleep end,number = 1
  • 确实是乱序执行的,符合多线程的特征
  • number 值全部都只 +1 了,都是2
    • 这里就关系到前文有提到的 Isolate 的特性之一:
      • 含有独立的内存空间
      • 这里每个 Isolate 内的 number 都是独立的

线程间通讯

这里的通讯需要通过端口 Port 来进行,并且完成后需要手动释放。
int number = 1;
void main() async {
print('${DateTime.now()}: 1');
// 创建端口
ReceivePort port = ReceivePort();
// Isolate
Isolate iso = await Isolate.spawn(isolateFunc, port.sendPort);
// 监听数据变化
port.listen((message) {
number = message;
print('${DateTime.now()}: 监听到 number = $number');
// 关闭端口
port.close();
// 销毁 Isolate
iso.kill();
});
}
void isolateFunc(SendPort send) {
sleep(Duration(seconds: 3));
send.send(15);
}
输出:
flutter: 2021-11-28 10:38:59.982321: 1
flutter: 2021-11-28 10:39:03.114209: 监听到 number = 15

compute

compute 是对 Isolate 的封装可以达到一样的效果:
int number = 0;
void main() async {
print('${DateTime.now()}: 1');
compute(comFunc, 1);
compute(comFunc, 2);
compute(comFunc, 3);
compute(comFunc, 4);
compute(comFunc, 5);
compute(comFunc, 6);
compute(comFunc, 7);
compute(comFunc, 8);
compute(comFunc, 9);
compute(comFunc, 10);
}
void comFunc(int value) {
number = value;
print('${DateTime.now()}: value = $value number = $number');
}
输出:
flutter: 2021-11-28 10:54:03.331916: value = 6 number = 6
flutter: 2021-11-28 10:54:03.337162: value = 5 number = 5
flutter: 2021-11-28 10:54:03.382666: value = 4 number = 4
flutter: 2021-11-28 10:54:03.404757: value = 1 number = 1
flutter: 2021-11-28 10:54:03.406688: value = 7 number = 7
flutter: 2021-11-28 10:54:03.424210: value = 3 number = 3
flutter: 2021-11-28 10:54:03.433246: value = 8 number = 8
flutter: 2021-11-28 10:54:03.447492: value = 2 number = 2
flutter: 2021-11-28 10:54:03.494157: value = 9 number = 9
flutter: 2021-11-28 10:54:03.495839: value = 10 number = 10

进程间通讯

可以通过返回值的形式进行:
int number = 0;
void main() async {
number = await compute(comFunc, 1);
print(number);
}
int comFunc(int value) {
sleep(Duration(seconds: 2));
return number + value;
}
输出:
flutter: 1

参考

https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a
https://www.youtube.com/watch?v=vl_AaCgudcY&t=53s