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

Last updated

Was this helpful?