众所周知,在 Flutter 中,await
是用来等待异步操作完成的关键字。它不会阻塞主线程,而是通过 Dart 的事件循环和 Future
对象的机制来实现异步代码的暂停与恢复。
当我们在使用await时候通常需要在函数名后面添加async关键字做标记,那么添加了aysnc后,flutter编译器或者说是flutter运行时做了些什么事情呢?
程序在 await
的异步操作完成后,会回到原先暂停的代码处并继续执行,那么flutter是如何保存这个代码恢复节点的?
带着这两个问题,我们寻找一下答案:
在 Flutter(和 Dart)中,当代码执行到 await
时,程序会暂停当前异步函数的执行,等待 Future
完成,然后在完成后继续执行。为了实现这种行为,Dart 语言使用了一种称为“状态机”(state machine)的机制来记录和管理代码执行的暂停点和恢复点。
状态机的工作原理
-
异步函数的转换:
- 当 Dart 编译器遇到一个带有
await
的异步函数时,它会将这个函数转换为一个状态机。 - 状态机是一个可以根据不同状态继续执行不同部分代码的机制。每次
await
操作会让状态机进入一个暂停状态,等待异步操作完成。
- 当 Dart 编译器遇到一个带有
-
状态保存:
- 当代码执行到
await
时,Dart 状态机会记录当前的执行状态,包括局部变量、当前执行的代码行(称为“代码节点”或“恢复点”),以及接下来要执行的代码。 - 这些信息被保存起来,以便在异步操作完成后可以恢复执行。
- 当代码执行到
-
事件循环与恢复执行:
- 异步操作通过返回一个
Future
对象,告诉 Dart 它将在未来某个时间点完成。当Future
完成时,它会通知 Dart 事件循环。 - 事件循环会将恢复状态机的任务放入任务队列中。一旦主线程空闲,事件循环会取出该任务,恢复状态机的执行。
- 异步操作通过返回一个
-
恢复执行:
- 状态机根据之前保存的状态,恢复到暂停的代码行,并继续执行后续的代码。
- 这种机制让异步函数在外部看来像是同步代码,但在内部实际是通过状态机和事件循环协调的非阻塞执行。
例子:状态机如何处理异步函数
考虑一个简单的异步函数:
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();
}
}
}
在这个转换后的代码中:
- 初始状态
_state
为0
,表示开始执行。当执行到await
时,状态机记录当前状态,并将其设置为1
,然后暂停执行。 - 当
Future.delayed
完成后,状态机会通过then
方法恢复执行,并且状态机会继续从状态1
开始执行。
编译时和运行时
将异步函数转换为状态机的过程是在 编译时 完成的,而不是在运行时。具体来说,当你编写一个使用 async
/await
的 Dart 函数时,Dart 编译器在编译代码时会将该函数转换为一个状态机,这样在运行时可以管理异步操作的暂停和恢复。
编译时的状态机转换
- 编译时转换:当 Dart 编译器编译代码时,它会分析带有
async
和await
关键字的异步函数,并将这些函数转换为状态机的形式。这个转换是在代码编译成字节码或本地代码时进行的,因此在运行时,代码已经被编译器处理成一种能够支持异步操作的状态机结构。 - 代码生成:在编译时,编译器生成的代码会包含状态管理的逻辑,例如保存当前执行的状态(或称为“暂停点”)、变量的状态,以及在适当的时间点恢复执行的逻辑。这些状态和恢复点在编译时就已经确定好,因此在运行时,Dart 运行时环境只需按照生成的状态机逻辑来执行代码。
运行时的执行
- 运行时执行:在运行时,Dart 虚拟机或运行时环境根据编译器生成的状态机代码执行异步函数。运行时不需要再处理状态机的生成,而是直接根据编译好的状态机代码执行每一个状态,处理
await
表达式,等待Future
完成并恢复执行。
优势
- 性能优化:由于状态机的转换是在编译时完成的,运行时的开销较小,执行效率较高。
- 代码简化:开发者只需编写简单的
async
/await
代码,而编译器负责将其转换为复杂的状态机,简化了开发的难度。
总结
Dart 使用状态机来管理异步函数的暂停和恢复。await
触发状态机记录当前执行状态,并在异步操作完成后恢复执行。这个机制确保了异步函数的执行看起来是顺序的,但实际上是通过非阻塞的方式在不同的状态间切换。状态机记录了每个暂停点(代码节点),并在异步操作完成后恢复执行原来的代码。
将异步函数转换为状态机的过程是在编译时完成的。编译器在编译过程中会分析和转换这些函数,使得在运行时,代码可以通过状态机来有效地管理异步操作的暂停和恢复。这种编译时的转换提高了代码的执行效率,同时让开发者的代码保持简单和易读。
尽管 Dart 官方文档没有直接使用“状态机”这一术语,但该术语可以帮助理解编译器和虚拟机如何处理异步操作的机制 dart.dev/libraries/a…