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

前言

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

Isolate

Isolate 是所有 Dart 代码运行的地方。它就像机器中的一个小空间,它拥有自己的内存块和运行事件循环单一线程

在其他语言中,比如 C++ 你可以让多个线程共享相同的内存。并运行他们想要的代码。但是在 Dart 中,每个线程都有自己的 Isolate 和它自己的内存,它只处理事件。

许多 Dart 应用程序,在单一 Isolate 中运行所有代码。但是如果需要,你可以使用多个 Isolate。

如果你要运行的计算量太过庞大,如果它在主要的 Isolate 中运行的话,会导致应用程序扔掉当前的栈帧(Drop Frame)

子Isolate

你可以使用 Isolate.spawn 或者 Flutter 的计算函数,两者都会创建一个单独的 Isolate 来进行运算。让你主要的 Isolate 可以不受影响。

新的 Isolate 将会获得独立的事件循环和自己的内存,即使原本的 Isolate 是这个新 Isolate 的母体,原本的 Isolate 也不可以访问它。

这就是 Isolate 名称的来源。这些小空间之间彼此隔离。

Isolate 间通讯

Isolate 间合作的唯一方式,是来回传递消息。

一个 Isolate 会给另一个 Isolate 发送消息,接收消息的 Isolate 会使用他的事件循环处理消息。

Isolate 为什么这样设计

缺乏共享内存,可能听起来有些严格。 特别如果你是使用 Java C++ 这样的语言的话。但它对 Dart 编译器有一些关键性的好处。

比如:

  • Isolate 的内存分配和垃圾回收不需要

  • 因为他只有单一线程,因此如果它不忙,你就知道内存没有发生变化。这对 Flutter 应用程序非常有用。它有时需要快速构建和拆解很多 Widget。

事件循环

想象一下,应用程序的生命,在时间轴中延伸。期间会有很多外部信息的输入,而我们并不能预知这些信息什么时候会输入。这时我们就需要一个永不阻塞的线程来处理所有事件,它运行一个事件循环。

他从事件队列中,取出最旧的事件,处理它,返回,再取出下一个事件处理,依次循环,直到队列清空。

当事件清空,事件循环就会进入等待,直到有新的事件加入队列。

所有高级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

Last updated