如何在 Flutter 中记录和恢复 await 异步操作的暂停点

313 阅读5分钟

众所周知,在 Flutter 中,await 是用来等待异步操作完成的关键字。它不会阻塞主线程,而是通过 Dart 的事件循环和 Future 对象的机制来实现异步代码的暂停与恢复。

当我们在使用await时候通常需要在函数名后面添加async关键字做标记,那么添加了aysnc后,flutter编译器或者说是flutter运行时做了些什么事情呢?

程序在 await 的异步操作完成后,会回到原先暂停的代码处并继续执行,那么flutter是如何保存这个代码恢复节点的?

带着这两个问题,我们寻找一下答案:

在 Flutter(和 Dart)中,当代码执行到 await 时,程序会暂停当前异步函数的执行,等待 Future 完成,然后在完成后继续执行。为了实现这种行为,Dart 语言使用了一种称为“状态机”(state machine)的机制来记录和管理代码执行的暂停点和恢复点。

状态机的工作原理

  1. 异步函数的转换

    • 当 Dart 编译器遇到一个带有 await 的异步函数时,它会将这个函数转换为一个状态机。
    • 状态机是一个可以根据不同状态继续执行不同部分代码的机制。每次 await 操作会让状态机进入一个暂停状态,等待异步操作完成。
  2. 状态保存

    • 当代码执行到 await 时,Dart 状态机会记录当前的执行状态,包括局部变量、当前执行的代码行(称为“代码节点”或“恢复点”),以及接下来要执行的代码。
    • 这些信息被保存起来,以便在异步操作完成后可以恢复执行。
  3. 事件循环与恢复执行

    • 异步操作通过返回一个 Future 对象,告诉 Dart 它将在未来某个时间点完成。当 Future 完成时,它会通知 Dart 事件循环。
    • 事件循环会将恢复状态机的任务放入任务队列中。一旦主线程空闲,事件循环会取出该任务,恢复状态机的执行。
  4. 恢复执行

    • 状态机根据之前保存的状态,恢复到暂停的代码行,并继续执行后续的代码。
    • 这种机制让异步函数在外部看来像是同步代码,但在内部实际是通过状态机和事件循环协调的非阻塞执行。

例子:状态机如何处理异步函数

考虑一个简单的异步函数:


Future<void> exampleFunction() async {
  print('Start');
  await Future.delayed(Duration(seconds: 2));
  print('End');
}

当 Dart 编译器看到这个函数时,它会将其转换为类似下面的状态机代码(伪代码):

class _ExampleFunctionStateMachine {
  int _state = 0;//默认状态为0

  Future<void> run() async {
    switch (_state) {
      case 0:
        print('Start');
        _state = 1;//将状态修改为1
        return Future.delayed(Duration(seconds: 2)).then((_) => run());//执行完异步任务后重新调用run方法
      case 1:
        print('End');
        return Future.value();
    }
  }
}

在这个转换后的代码中:

  • 初始状态 _state0,表示开始执行。当执行到 await 时,状态机记录当前状态,并将其设置为 1,然后暂停执行。
  • Future.delayed 完成后,状态机会通过 then 方法恢复执行,并且状态机会继续从状态 1 开始执行。

编译时和运行时

将异步函数转换为状态机的过程是在 编译时 完成的,而不是在运行时。具体来说,当你编写一个使用 async/await 的 Dart 函数时,Dart 编译器在编译代码时会将该函数转换为一个状态机,这样在运行时可以管理异步操作的暂停和恢复。

编译时的状态机转换

  • 编译时转换:当 Dart 编译器编译代码时,它会分析带有 asyncawait 关键字的异步函数,并将这些函数转换为状态机的形式。这个转换是在代码编译成字节码或本地代码时进行的,因此在运行时,代码已经被编译器处理成一种能够支持异步操作的状态机结构。
  • 代码生成:在编译时,编译器生成的代码会包含状态管理的逻辑,例如保存当前执行的状态(或称为“暂停点”)、变量的状态,以及在适当的时间点恢复执行的逻辑。这些状态和恢复点在编译时就已经确定好,因此在运行时,Dart 运行时环境只需按照生成的状态机逻辑来执行代码。

运行时的执行

  • 运行时执行:在运行时,Dart 虚拟机或运行时环境根据编译器生成的状态机代码执行异步函数。运行时不需要再处理状态机的生成,而是直接根据编译好的状态机代码执行每一个状态,处理 await 表达式,等待 Future 完成并恢复执行。

优势

  • 性能优化:由于状态机的转换是在编译时完成的,运行时的开销较小,执行效率较高。
  • 代码简化:开发者只需编写简单的 async/await 代码,而编译器负责将其转换为复杂的状态机,简化了开发的难度。

总结

Dart 使用状态机来管理异步函数的暂停和恢复。await 触发状态机记录当前执行状态,并在异步操作完成后恢复执行。这个机制确保了异步函数的执行看起来是顺序的,但实际上是通过非阻塞的方式在不同的状态间切换。状态机记录了每个暂停点(代码节点),并在异步操作完成后恢复执行原来的代码。

将异步函数转换为状态机的过程是在编译时完成的。编译器在编译过程中会分析和转换这些函数,使得在运行时,代码可以通过状态机来有效地管理异步操作的暂停和恢复。这种编译时的转换提高了代码的执行效率,同时让开发者的代码保持简单和易读。

尽管 Dart 官方文档没有直接使用“状态机”这一术语,但该术语可以帮助理解编译器和虚拟机如何处理异步操作的机制 dart.dev/libraries/a…