本文字数::
36130
字
预计阅读时间:
91
分钟
在
Android
应用中, 用户时常会遇到界面卡顿的情况,非常影响用户的体验。作为
Android
开发肯定都知道:应用在主线程里做了大量的耗时操作(例如文件读写, 数据库读写,网络访问,图片编解码等),就会导致
UI
不能及时刷新,出现跳帧现象。如果应用主线程不能及时处理用户的输入事件或广播消息,系统甚至会直接弹出著名的
ANR
对话框,提示用户杀死应用。
在
Flutter
应用中,如果出现界面卡顿,它的原因也是如此吗?
我们带着这些疑问,一起来搞清楚
Flutter
的线程模型和异步原理,并找到问题的答案。
一、Flutte
r系统结
构
首先,我们熟悉下
Flutter
官方提供的系统结构图:
整体框架是采用分层设计的,自上而下分别是:
Framework
、
Engine
、
Embedder
。
-
Framework
:基于
Dart
语言构建的
Framework
,包括了各种
UI
组件,动画和手势识别等,并将所有的设计通过
Widgets
小组件层进行抽象封装。所以在
Flutter
中一切都是
Widget
; -
Engine
:基于
C/C++
构建的引擎,包括了
Skia
、
Dart
和
Text
等,实现了
Flutter
渲染引擎,文字排版,事件处理和
Dart
运行时等功能。
Skia
和
Text
为上层提供调用底层渲染和排版的功能,
Dart
运行时提供了调用
Dart
和渲染引擎的能力; -
Embedder
:嵌入层是操作系统适配层,会将
Flutter
嵌入到各个平台上。嵌入层负责适配原生平台插件、线程管理、渲染
Surface
设置等。
从架构图中可以看到,
Embedder
负责线程的创建和管理,并且提供
Task Runner
给
Engine
使用。
Engine
虽然自己并不创建和管理线程,但是它通过
Dart VM(Dart Runtime Mgmt)
提供
Isolate
给
Framework
和应用层进行多线程创建。
二、Task Runner
接下来,我们继续了解
Embeder
提供的四个
Task Runner
:
Platform Runner
,
UI Runner
,
GPU Runner
,
IO Runner
。
Flutter
的代码基本上由这四个
Runner
负责运行,每个
Runner
负责不同的任务, 不只处理
Engine
的任务,还处理
Native Plugin
带来的原生平台任务。
在
Android
中,每个
Flutter Engine
各自拥有一个
UI Runner
、
GPU Runner
、
IO Runner
,但是一个应用中的所有
Engine
共享一个
Platform Runner
。每个
Runner
都是一个平台线程,且
Engine
会将
UI Runner
和
Root Isolate
进行互相绑定。但
Runner
和
Isolate
本身是相互独立的,
Isolate
由
Dart VM
进行管理,不由
Runner
管理。
-
Platform Runner
Platform Runner
运行在平台的
Main Thread
,负责执行
Flutter Engine
的代码和
Native Plugin
任务。
如果在
Platform Runner
中运行耗时任务,会影响原生平台任务的正常执行。但是
Platform Runner
被阻塞后并不会导致页面卡顿。因为
Platform Runner
不负责
Flutter
的页面渲染,这点和
Android
原生应用不一样。
-
UI Runne
r
UI Runner
负责为
Engine
执行
Root Isolate
的代码,而
Root Isolate
负责运行所有
Dart
代码。
Root Isolate
绑定了很多
UI Runner
的处理函数,负责创建管理
Layer Tree
最终绘制到屏幕上的内容,因此这个线程被阻塞会直接导致界面卡顿掉帧。
每当页面更新的
vsync
到来时,
Root Isolate
会对
Widgets
进行
layout
,生成
Layer tree
等页面显示信息,提交给
Engine
去处理。
所以,在
Root Isolate
中运行耗时任务会导致页面显示卡顿。
-
GPU Runn
er
GPU Runner
负责将
UI Runner
提供的
Layer Tree
信息转化为平台可执行的
GPU
指令,并提交给渲染平台,如
Skia
。
GPU Runner
还负责管理绘制所需要的
GPU
资源,比如平台
Framebuffer
,
Surface
,
Texture
和
Buffers
等。
GPU Runner
相对比较独立,除了
Embedder
层
Runner
线程外,其他线程均不可向其提交渲染信息。
-
IO Runner
IO Runner
负责将读取的图片解压转换成
GPU
能够处理的格式并提交给
GPU Runner
处理。
当
Image
这样的资源通过
async call
调用时,
Framework
会通知
IO Runner
进行图片的异步加载,进行格式处理,然后通过
GPU Runner
的
Context
引用,提交给
GPU Runner
处理。
由上可知,在
Android Flutter
应用中,如果出现界面卡顿,它的原因和
Android
应用的原因并不相同。
Flutter
应用中平台线程的阻塞不会影响界面的卡顿,而
UI Runner
的阻塞必然导致页面卡顿。
那么,在
Flutter
应用中,像网络请求,文件读取,海量计算,图片处理,编解码等耗时任务都应该怎么处理,才能不阻塞
UI Runner
呢?
要解释清楚这个问题,我们需要先了解
Flutter
中的线程模型:
Isolate
。
三、线程模型
Isolate
是
Dart
平台对线程的实现方案,所以和线程一样,也可以利用多核
CPU
去处理大量耗时任务。
Isolate
底层实际还是使用操作系统提供的
OSThread
。但和普通
Thread
不同,
Isolate
拥有独立的内存,由线程加独立内存构成。
由于
Isolate
线程之间内存不共享,所以
Isolate
线程之间并不存在共享数据的问题,所以也不需要
Isolate
数据同步机制。
Isolate
之间虽然不能共享数据,但是可以通过端口 Port 的方式进行数据通信。
3.1 Isolate
在
Android Flutter
应用启动后,会首先执行
main
函数,接着调用
runApp
,然后创建一个
Flutter Engin
。
Engin
会启动四个
Task Runner
,并且
UI Runner
开始执行
Root Isolate
主线程中的代码。
Android Flutter
main
runApp
Flutter Engin
Engin
Task Runner
UI Runner
Root Isolate
Flutter
应用默认在单线程中运行
Dart
代码,如果不开启新的线程,所有
Dart
代码都在
Root Isolate
主线程中运行。
我们可以通过
Isolate
的创建和
Port
数据通信的例子来进一步了解代码执行的线程情况:
// kiki_main_tab_page.dart
class KKMainTabPageState extends BaseThemeState<KKMainTabPage>with WidgetsBindingObserver {
// 这是摸鱼kik的主界面
......
@override
void initState() {
super.initState();
......
//测试 Future 中代码的运行所属 Isolate
Future.delayed(const Duration(milliseconds: 500))
.then((value) {
KKMethodChannelUtil.setAndroidStatusBarTheme(AppScreenMedia.isLight);
print_cjf('initState 1---' + Isolate.current.debugName.toString());
});
print_cjf('initState 2---' + Isolate.current.debugName.toString());
//测试在新的 isolate 中请求数据。此处没有用await等待方法的异步结果。
testIsolate();
print_cjf('initState 3---' + Isolate.current.debugName.toString());
}
@override
Widget build(BuildContext context) {
print_cjf('build 4---' + Isolate.current.debugName.toString());
......
}
......
}
// test_isolate.dart
void testIsolate() async {
print_cjf('testIsolate start---' + Isolate.current.debugName.toString());
//创建 ReceivePort,用来接受新 Isolate 发送的消息
ReceivePort receivePort1 = ReceivePort();
print_cjf('testIsolate 1---' + Isolate.current.debugName.toString());
//创建新的 Isolate,并且把 receivePort1 的发送端口传给 newIsolate
var newIsolate = await Isolate.spawn(dataLoader, receivePort1.sendPort);
print_cjf('testIsolate 2---' + Isolate.current.debugName.toString());
//等待 newIsolate 发送的异步消息(异步消息只有 newIsolate 的发送端口 receivePort3.sendPort)。
//通过 first 获得异步消息后,会立即关闭 receivePort1 的 sendPort 端口。
SendPort sendPort3 = await receivePort1.first;
print_cjf('testIsolate 3---' + Isolate.current.debugName.toString());
//因为上面 first 函数获取异步消息后,关闭了 receivePort1 的 sendPort 端口
//所以要新创建一个新的 ReceivePort 接受 newIsolate 发送过来的消息
ReceivePort receivePort2 = ReceivePort();
print_cjf('testIsolate 4---' + Isolate.current.debugName.toString());
//使用 newIsolate 的发送端口 sendPort3,发送异步消息(异步消息包括网络 url,和 rootIsolate 的 receivePort2 发送端口)
sendPort3.send(['https://jsonplaceholder.typicode.com/posts/1',receivePort2.sendPort]);
print_cjf('testIsolate 5.1---' + Isolate.current.debugName.toString());
sendPort3.send(["https://w.sohu.com/detail/1",receivePort2.sendPort]);
print_cjf('testIsolate 5.2---' + Isolate.current.debugName.toString());
sendPort3.send(["aaaaaa",receivePort2.sendPort]);
print_cjf('testIsolate 5.3---' + Isolate.current.debugName.toString());
sendPort3.send(["bbbbbb",receivePort2.sendPort]);
print_cjf('testIsolate 5.4---' + Isolate.current.debugName.toString());
//获取 newIsolate 发送来的异步消息(异步获取的网络数据)
//方法一: 不等待 循环获取异步数据
receivePort2.listen((msg) {
print_cjf('testIsolate 6.2---' + Isolate.current.debugName.toString() + '---$msg');
});
print_cjf('testIsolate end---' + Isolate.current.debugName.toString());
}
void dataLoader(SendPort sendPort1) async {
print_cjf('dataLoader start---' + Isolate.current.debugName.toString());
ReceivePort receivePort3 = ReceivePort();
sendPort1.send(receivePort3.sendPort);
print_cjf('dataLoader 1---' + Isolate.current.debugName.toString());
await for (var msg in receivePort3) {
String dataUrl = msg[0];
SendPort sendPort2 = msg[1];
// 暂不从网络中获取数据,直接模拟一个数据返回
sendPort2.send('dataLoader 2.1---' + Isolate.current.debugName.toString() + "---$dataUrl");
print_cjf('dataLoader 3.1---' + Isolate.current.debugName.toString() + "---$dataUrl");
if(dataUrl.startsWith("aaaaaa")) {
//销毁当前 isolate, 并发送结束消息给 sendPort2
Isolate.exit(sendPort2, "dataLoader 4 ---Isolate exit with last message to sendPort2");
}
}
print_cjf('dataLoader end---' + Isolate.current.debugName.toString()); //不会被执行到。
}
// 输出 log
I/flutter ( 4910): 11:25:56:587:cjf---initState 2---main //KKMainTabPageState 的 initState, 开始调用
I/flutter ( 4910): 11:25:56:588:cjf---testIsolate start---main
I/flutter ( 4910): 11:25:56:589:cjf---testIsolate 1---main //调用 await Isolate.spawn 创建新 isolate,testIsolate 方法中断执行,需等待异步结果回来后恢复执行。
I/flutter ( 4910): 11:25:56:590:cjf---initState 3---main //KKMainTabPageState 的 initState,await Isolate.spawn 会进行异步调度,所以会返回到 initState 中继续执行
I/flutter ( 4910): 11:25:56:591:cjf---build 4---main //KKMainTabPageState 的 build,后续还会被多次调用
I/flutter ( 4910): 11:25:56:592:cjf---testIsolate 2---main //异步执行,await Isolate.spawn 的异步结果返回,testIsolate 后续代码继续执行
I/flutter ( 4910): 11:25:57:086:cjf---initState 1---main //异步执行,Future.delayed 时间到了后,异步任务会得到执行,且运行在 Root Isolate 中
I/flutter ( 4910): 11:25:57:133:cjf---dataLoader start---dataLoader//新建 isolate 内部, 新的 isolate 开始运行
I/flutter ( 4910): 11:25:57:134:cjf---dataLoader 1---dataLoader //新建 isolate 内部, 将自己的 SendPort3 发送给 RootIsolate 的 SendPort1
I/flutter ( 4910): 11:25:57:140:cjf---testIsolate 3---main //异步执行,await receivePort1.first,仍然在 root isolate 中
I/flutter ( 4910): 11:25:57:141:cjf---testIsolate 4---main
I/flutter ( 4910): 11:25:57:142:cjf---testIsolate 5.1---main //给 SendPort3 发送 jsonplaceholder.typicode.com 数据和 sendPort2
I/flutter ( 4910): 11:25:57:144:cjf---testIsolate 5.2---main //给 SendPort3 发送 w.sohu.com 数据和 sendPort2
I/flutter ( 4910): 11:25:57:144:cjf---dataLoader 3.1---dataLoader---https://jsonplaceholder.typicode.com/posts/1
I/flutter ( 4910): 11:25:57:144:cjf---testIsolate 5.3---main //给 SendPort3 发送 aaaaaa 数据和 sendPort2
I/flutter ( 4910): 11:25:57:145:cjf---dataLoader 3.1---dataLoader---https://w.sohu.com/detail/1
I/flutter ( 4910): 11:25:57:145:cjf---testIsolate 5.4---main //给 SendPort3 发送 bbbbbb 数据和 sendPort2
I/flutter ( 4910): 11:25:57:146:cjf---testIsolate end---main //设置 listen 数据,退出 testIsolate 方法
I/flutter ( 4910): 11:25:57:146:cjf---dataLoader 3.1---dataLoader---aaaaaa
I/flutter ( 4910): 11:25:57:148:cjf---testIsolate 6.2---main---dataLoader 2.1---dataLoader---https://jsonplaceholder.typicode.com/posts/1
I/flutter ( 4910): 11:25:57:149:cjf---testIsolate 6.2---main---dataLoader 2.1---dataLoader---https://w.sohu.com/detail/1
I/flutter ( 4910): 11:25:57:149:cjf---testIsolate 6.2---main---dataLoader 2.1---dataLoader---aaaaaa
I/flutter ( 4910): 11:25:57:150:cjf---testIsolate 6.2---main---dataLoader 4 ---Isolate exit with last message to sendPort2
I/flutter ( 4910): 11:25:57:588:cjf---build 4---main //KKMainTabPageState 的 build,被多次调用
可以从上述例子,
Log
和注释可以看到:
-
Root Isolate
的
debugName
也是
"main"
,而不是
"root"
; -
通过
Isolate.spawn
创建新的
Isolate
,
debugName
即为方法名
"dataLoader"
; -
除了新建
Isolate
中的代码,其它
Dart
代码都默认运行在
Root Isolate
中,包括
widget
中的代码,
Future
中的代码,和
await
异步方法中的代码。如:
"initState 3---main"
,
"build 4---main"
,
"initState 1---main"
,
"testIsolate 1---main"
都在
"main"
线程中运行; -
await
方法调用,需要等待异步任务结果,会停止
当前方法
后续代码的执行,但不会阻塞
调用者
后续代码的执行,如
log "initState 3"
的输出并没有等待
log "testIsolate 2"
的输出。包括
widget
的构建
log "build 4"
, 和
initState
中的
Future delay
异步任务的
log "initState 1"
都得到了执行,而不是一直阻塞等待
testIsolate
方法中的
await Isolate.spawn
和
await receivePort1.first
语句的返回; -
Isolate
之间通过
ReceivePort
进行信息发送和接送,且消息传递的过程是异步的。如,
log "testIsolate 5.1"
,
"testIsolate 5.2"
,
"testIsolate 5.3"
,
"testIsolate 5.4"
和
log "dataLoader 3.1"
是穿插着打印出来的; -
Isolate
之间可以通过
await receivePort1.first
来阻塞式接收一次消息,或通过
receivePort2.listen
来非阻塞式循环接收信息,或通过
await for receivePort3
来阻塞式循环接收信息。
从例子中我们不但可以看到
Isolate
的创建和
Port
数据通信方式,同时也看到了
Dart
代码都默认运行在
Root Isolate
主线程中。
3.2 compute
上面例子中创建一个
Isolate
进行数据通讯,步骤较为麻烦。为此
Flutter
为我们提供了一个简版的
Isolate
:
compute
。
compute
很适合简单,高
CPU
的耗时任务处理,它可以很便捷地进行多线程任务开发,我们举例进行说明:
// test_isolate.dart
void testCompute() async {
print_cjf('testCompute start---' + Isolate.current.debugName.toString());
Future.delayed(new Duration(seconds: 1),(){
print_cjf('testCompute 1---' + Isolate.current.debugName.toString());
});
var count = await compute(countTask, 1234567890);
print_cjf('testCompute end---' + Isolate.current.debugName.toString()+ "---count=" + count.toString());
}
int countTask(int num1) {
print_cjf('countTask start ---' + Isolate.current.debugName.toString());
int count = 0;
while (num1 > 0) {
if (num1 % 2 == 0) { count++; }
num1--;
}
print_cjf('countTask end---' + Isolate.current.debugName.toString());
return count;
}
// 输出 Log
I/flutter ( 4910): 12:44:32:731:cjf---testCompute start---main
I/flutter ( 4910): 12:44:33:254:cjf---countTask start ---Closure: (int) => int from Function 'countTask': static.
I/flutter ( 4910): 12:44:33:736:cjf---testCompute 1---main
I/flutter ( 4910): 12:45:16:329:cjf---countTask end---Closure: (int) => int from Function 'countTask': static.
I/flutter ( 4910): 12:45:16:330:cjf---testCompute end---main---count=617283945
compute
方法是
Flutter foundation
包中的顶层方法,它对
Isolate.spawn
及
sendPort
消息发送做了封装,返回一个异步结
Future<R>
。
通过封装后的接口,我们只需简单调用
await compute
,就可以等待一个耗时 43s 的计算任务的异步执行结果,并且等待过程中没有阻塞
Root Isolate
中其它异步任务地执行,比如,通过
Future.delayed
插入的异步任务
"testcompute 1---main"
,在
countTask Isolate
执行的同时,在
Root Isolate
中被执行了。
3.3 Memory
上面 2 个例子,要么是直接等待新
Isolate
的异步结果,要么是通过
port
进行数据传输,并没有使用全局数据进行共享,所有也没有数据同步问题。
实际上在
Flutter
中确实没有类似
Android
的全局共享数据,因为
Flutter
中的
Isolate
拥有独立的内存,数据没法共享,只能通过
port
传输。
我们继续通过一个简单的例子,来说明
Isolate
独立内存这个特点:
// test_isolate.dart
int intValue = 0; //定义普通 int 顶层变量
IntObject intObject = IntObject(); //定义对象 IntObject 顶层变量
class IntObject {
int _i = 0;
void increase() { _i++; }
int get() { return _i;}
}
void testIsolateMemory() async {
print_cjf('testIsolateMemory start');
final receive = ReceivePort();
receive.listen((msg) {
//打印 MemoryTask Isolate 传过来的String
print_cjf('testIsolateMemory ---'+ "data===$msg");
//打印 Root Isolate 中的变量值
print_cjf('testIsolateMemory ---'+ "i=$intValue, intObject=${intObject.get()}");
});
//5s后,给顶层变量加1
Future.delayed(const Duration(seconds: 5),(){
intValue++;
intObject.increase();
print_cjf('testIsolateMemory ---'+ "delayed:i=$intValue, intObject=${intObject.get()}");
});
Isolate isolate = await Isolate.spawn(MemoryTask, receive.sendPort);
print_cjf('testIsolateMemory end');
}
void MemoryTask(SendPort sendPort) {
int counter = 0; //MemoryTask Isolate 中的局部变量
print_cjf('MemoryTask start');
//每隔 1s,给顶层变量和局部变量都加1
Timer.periodic(const Duration(seconds: 1), (_) {
counter++;
intValue++;
intObject.increase();
String sendMsg = "counter=$counter, i=$intValue, intObject=${intObject.get()}";
//打印 MemoryTask Isolate 中的变量值
print_cjf('MemoryTask ---$sendMsg');
sendPort.send(sendMsg);
if(counter >= 10) {
//销毁当前 isolate, 并发送结束消息给 sendPort
Isolate.exit(sendPort, "MemoryTask ---Isolate exit with last message to sendPort");
}
});
print_cjf('MemoryTask end');
}
// 输出 Log
I/flutter ( 4910): 12:58:21:555:cjf---testIsolateMemory start
I/flutter ( 4910): 12:58:21:575:cjf---testIsolateMemory end
I/flutter ( 4910): 12:58:22:84:cjf---MemoryTask start
I/flutter ( 4910): 12:58:22:86:cjf---MemoryTask end
I/flutter ( 4910): 12:58:23:93:cjf---MemoryTask ---counter=1, i=1, intObject=1//MemoryTask Isolate 中的顶层
变量发生改变
I/flutter ( 4910): 12:58:23:96:cjf---testIsolateMemory ---data===counter=1, i=1, intObject=1
I/flutter ( 4910): 12:58:23:97:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:24:87:cjf---MemoryTask ---counter=2, i=2, intObject=2
I/flutter ( 4910): 12:58:24:89:cjf---testIsolateMemory ---data===counter=2, i=2, intObject=2
I/flutter ( 4910): 12:58:24:90:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:25:89:cjf---MemoryTask ---counter=3, i=3, intObject=3
I/flutter ( 4910): 12:58:25:90:cjf---testIsolateMemory ---data===counter=3, i=3, intObject=3
I/flutter ( 4910): 12:58:25:91:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:26:89:cjf---MemoryTask ---counter=4, i=4, intObject=4
I/flutter ( 4910): 12:58:26:90:cjf---testIsolateMemory ---data===counter=4, i=4, intObject=4
I/flutter ( 4910): 12:58:26:91:cjf---testIsolateMemory ---i=0, intObject=0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:26:621:cjf---testIsolateMemory ---delayed:i=1, intObject=1 //Root Isolate 中给顶层变量加1
I/flutter ( 4910): 12:58:27:88:cjf---MemoryTask ---counter=5, i=5, intObject=5
I/flutter ( 4910): 12:58:27:90:cjf---testIsolateMemory ---data===counter=5, i=5, intObject=5
I/flutter ( 4910): 12:58:27:90:cjf---testIsolateMemory ---i=1, intObject=1 //Root Isolate 中的顶层变量发生改变
I/flutter ( 4910): 12:58:28:89:cjf---MemoryTask ---counter=6, i=6, intObject=6
I/flutter ( 4910): 12:58:28:90:cjf---testIsolateMemory ---data===counter=6, i=6, intObject=6
I/flutter ( 4910): 12:58:28:91:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:29:87:cjf---MemoryTask ---counter=7, i=7, intObject=7
I/flutter ( 4910): 12:58:29:89:cjf---testIsolateMemory ---data===counter=7, i=7, intObject=7
I/flutter ( 4910): 12:58:29:90:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:30:89:cjf---MemoryTask ---counter=8, i=8, intObject=8
I/flutter ( 4910): 12:58:30:91:cjf---testIsolateMemory ---data===counter=8, i=8, intObject=8
I/flutter ( 4910): 12:58:30:92:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:31:89:cjf---MemoryTask ---counter=9, i=9, intObject=9
I/flutter ( 4910): 12:58:31:91:cjf---testIsolateMemory ---data===counter=9, i=9, intObject=9
I/flutter ( 4910): 12:58:31:91:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:32:89:cjf---MemoryTask ---counter=10, i=10, intObject=10
I/flutter ( 4910): 12:58:32:90:cjf---testIsolateMemory ---data===counter=10, i=10, intObject=10
I/flutter ( 4910): 12:58:32:91:cjf---testIsolateMemory ---i=1, intObject=1
I/flutter ( 4910): 12:58:32:91:cjf---testIsolateMemory ---data===MemoryTask ---Isolate exit with last message to sendPort
I/flutter ( 4910): 12:58:32:92:cjf---testIsolateMemory ---i=1, intObject=1
我们从输出
log
可以看出,
-
MemoryTask Isolate
中,每隔 1s 就给顶层变量加 1,但是,
Root Isolate
中的变量并不会同步改变; -
Root Isolate
中 5s 后给顶层变量加 1,也只影响了
Root Isolate
中读取的顶层变量值,不会影响到
MemoryTask Isolate
中读取的顶层变量值。
由此可见,跨
Isolate
数据共享只能通过
port
的方式。
Isolate
中的内存是独立的,它们不存在数据共享,当然也就不需要处理共享数据的线程同步问题。
Isolate
中内存独立的特点,和
Java
中的线程局部存储
ThreadLocal
有点类似。
Android
中
Looper
类里的静态变量
sThreadLocal
,就属于线程局部存储,不同线程中调用
Looper.prepare
设置到
sThreadLocal
中的
Looper
对象各不相同,取出来使用的对象也不相同。我们可以参考对比其源码,如下:
//Looper.java
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
......
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
......
}
通过
Dart
源码也可以看到,
Isolate
的创建过程,包括了创建
Isolate
结构体,在堆中分配线程内存,创建线程和使用线程池等代码,也进一步说明了
Isolate
是具有独立内存的线程。
了解完
Isolate
线程模型,我们就明白了:我们所写的
Dart
代码默认是运行在
Root Isolate
中,而
Root Isolate
是运行在
UI Runner
上的,所以如果我们写的
Dart
代码过于耗时,必然导致负责管理绘制的
UI Runner
不能及时刷新页面,导致页面卡顿。因此,在
Flutter
应用中也需要使用多线程去处理耗时任务。
但是已有的使用经验中,我们并没有单独创建
Isolate
去处理网络请求任务,这与上述结论不就矛盾了吗?
我们先继续学习
Flutter
的异步原理,再来解答这个问题。
四、异步原理
Flutter
中,如果不单独创建
Isolate
的话,可以说是单线程模型,它通过
单
线程异步
方案支持大量并发操作。
大家容易把异步,并行和并发这几个概念搞混。我们先来了解下并行和并发的概念:
-
并行
:
指的是多个
CPU
,在同一时间里执行多个任务; -
并发
:
指的是一个
CPU
,轮换着去处理多个任务,由
系统
来管理任务的切换。
并行的实现
,必须由多线程来完成。多线程的方式可以利用多
CPU
的并行优势,同时执行多个任务。一般通过线程池来管理和复用大量线程。
并发的实现
,
多采用
单线程+非阻塞+事件通知
的方式来完成。因为线程切换的消耗是比较大的,不适合大量创建,所以并发的实现多采用
单线程
。阻塞式中断是在执行任务时将线程阻塞,等待执行完成后再恢复线程执行,无法达到异步效果。而非阻塞式中断是在执行任务时,保存当前上下文,不等待任务结果,继续调度当前线程的其它任务。调度当前线程的其它任务就依赖于事件通知。
而
异步
是相对于同步来说,它指程序的执行顺序与任务的排列顺序是不一致的。异步属于并发,不属于并行。
上面的描述,可能比较抽象,不易理解,我们继续通过图和示例来学习
Flutter
的异步原理。
4.1 事件循环和消息队列
在
Flutter
中,
Isolate
是通过事件循环和消息队列来实现异步的。每个
Isolate
包含一个事件循环以及两个事件队列:
-
Event Loop
:事件循环,负责无限循环读取微任务队列和事件队列进行处理; -
Microtask queue
:微任务事件队列,优先级比
Event queue
高,应用可以向
Isolate
添加微任务; -
Event queue
:普通事件队列,包括
IO
事件,绘制事件,手势事件,及应用添加的外部事件。
事件循环和事件队列的执行流程如下图:
在
Root Isolate
中,
Event queue
包括了绘制事件和手势事件,如果它们不能得到及时处理,会导致渲染、手势响应延时,出现卡顿现象。
为了保证渲染和手势得到及时响应,我们应该尽量不要向
Microtask queue
中添加事件,因为它的处理优先级比
Event queue
要高。
即便向
Event queue
中添加事件时,也不能添加过于耗时的事件,避免影响后续的渲染和手势事件得不到及时响应,影响用户使用体验。
我们可以通过
Future
和
await
向
Event queue
中插入任务,也可以通过
scheduleMicrotask
向
Microtask queue
添加任务。
下面通过举例,进一步进行说明:
// test_isolate.dart
void testMicroTask() async{
print_cjf('testMicroTask start');
new Future(() => print_cjf('future 1-1')) //创建异步任务 future 1
.then((_){
new Future(()=>print_cjf('future 2')); //创建异步任务 future 2
scheduleMicrotask(() =>print_cjf('microtask 3'));//创建异步任务 microtask 3。比同层的 future 2 先执行
print_cjf('future 1-2');
})
.then( (_)=>print_cjf('future 1-3') );
scheduleMicrotask(() =>print_cjf('microtask 4'));//创建异步任务 microtask 4。 比同级别的 future 1 先执行
print_cjf('testMicroTask 1');
await funDelay(); //创建异步任务 funDelay。调用耗时方法,使用 await 等待其结果返回
print_cjf('testMicroTask end'); // 异步执行
}
Future<int> funDelay() async{ //声明异步方法, 必须使用 async
print_cjf('funDelay start'); // 同步执行
await Future.delayed(Duration(seconds: 1)); //见 log1,调用 1s 耗时方法,使用 await 等待
// await Future.delayed(Duration(milliseconds: 1)); //见 log2,调用 1ms 耗时方法,使用 await 等待
print_cjf('funDelay end'); // 异步执行
return 2;
}
//输出 log1
I/flutter ( 4910): 17:51:25:216:cjf---testMicroTask start
I/flutter ( 4910): 17:51:25:216:cjf---testMicroTask 1 //同步执行
I/flutter ( 4910): 17:51:25:216:cjf---funDelay start //同步执行
I/flutter ( 4910): 17:51:25:217:cjf---microtask 4 //第一层 microtask
I/flutter ( 4910): 17:51:25:217:cjf---future 1-1 //第一层 future
I/flutter ( 4910): 17:51:25:218:cjf---future 1-2 //第一层 future 的链式调用
I/flutter ( 4910): 17:51:25:218:cjf---future 1-3 //第一层 future 的链式调用
I/flutter ( 4910): 17:51:25:218:cjf---microtask 3 //第二层 microtask
I/flutter ( 4910): 17:51:25:218:cjf---future 2 //第二层 future
I/flutter ( 4910): 17:51:26:218:cjf---funDelay end //属于第一层 future,但由于时间条件不满足,比第二层future更后执行了
I/flutter ( 4910): 17:51:26:219:cjf---testMicroTask end //属于第一层 future
//输出 log2
I/flutter ( 9669): 17:58:34:948:cjf---testMicroTask start
I/flutter ( 4910): 17:58:34:950:cjf---testMicroTask 1 //同步执行
I/flutter ( 9669): 17:58:34:950:cjf---funDelay start //同步执行
I/flutter ( 9669): 17:58:34:951:cjf---microtask 4//第一层 microtask
I/flutter ( 9669): 17:58:35:94:cjf---future 1-1//第一层 future
I/flutter ( 9669): 17:58:35:95:cjf---future 1-2//第一层 future 的链式调用
I/flutter ( 9669): 17:58:35:96:cjf---future 1-3//第一层 future 的链式调用
I/flutter ( 9669): 17:58:35:96:cjf---microtask 3//第二层 microtask
I/flutter ( 9669): 17:58:35:97:cjf---funDelay end //属于第一层 future
I/flutter ( 9669): 17:58:35:97:cjf---testMicroTask end //属于第一层 future
I/flutter ( 9669): 17:58:35:97:cjf---future 2//第二层 future
上述例子中,在
funDelay
等待 1s 和 1ms 的情况,分别输出了
log1
和
log2
。通过对比输出的
log
, 我们可以清晰的看到:
-
Microtask
和
Future
添加的任务都会被异步执行; -
同层的
Microtask
会比
Future
优先被处理。因为
new Future
创建的异步事件被添加到了
Event queue
,而
scheduleMicrotask
创建的异步事件被添加到了
MicroTask queue
中,而事件循环总是会优先处理
MicroTask queue
中的事件; -
await funDelay
后的代码
"funDelay end"
,属于第一层
future
; -
await
的运行受返回结果的时机影响:
-
如
log1
中,
await funDelay
需要 1s 时,事件循环处理到它时,发现时间条件不满足后跳过,所以
"funDelay end"
会比第二层的
"future 2"
后执行; -
如
log2
中,如果
await funDelay
只需要 1ms 时,
"funDelay end"
就会比第二层的
"future 2"
先执行; -
但无论是
log1
还是
log2
,
"funDelay end"
都会比第二层的
"microtask3"
后执行,因为
microtask
总是会被优先执行,即使更后被添加到
microtask
队列。
在上述例子中,
await funDelay
调用也会将后续代码处理事件,如
"testMicroTask end"
,添加到
Event queue
,属于第一层事件,会比第二层事件
"future 2"
在
Event queue
中的位置更靠前,如果条件符合的话,会比第二层的
"future 2"
事件更先被执行。
"microtask 3"
虽然是第二层
MicroTask
,但是在第一层
"future 1"
执行时被添加到
Microtask queue
中。当
"future 1 "
执行完,回到
Event loop
循环时,会优先处理
Microtask queue
中的事件,所以第二层的
"microtask 3"
会比 第一层的
"funDelay end"
先执行。
下面用图来更形象地描述上述例子,我们可以更清晰的看到事件队列的添加和消费情况:
通过上面的示例,我们可以看到在
Flutter
中进行异步调用,一般需要用到三个关键词:
Future
,
async
,
await
。其中
async
和
await
需要一起使用。
-
Future
:
Future
表示异步操作的返回结果,可以通过
then
处理返回结果。和
Java
中的
Future
功能差别巨大,注意区别使用; -
async
:标记某个方法为异步方法,在声明方法的时候使用。其返回值是
Future
类型; -
await
:表示等待某个异步方法的结果,一般调用耗时异步方法时使用。
下面我们继续深入
Future
,
async
,
await
的使用和原理学习。
4.2 Future
在
Dart
中可以通过
Future
进行异步操作,
Future
异步的特点是提供了链式调用,可以解决回调地狱,异常捕获,和代码执行依赖等问题。
Future<T>
表示一个指定类型的异步操作结果,如果不需要结果可以使用
Future<void>
。我们可以直接使用
Future
创建一个异步操作结果,也可以通过调用
async
方法得到一个异步操作结果。
-
当创建一个异步事件时,会直接返回一个
Future
,后续代码可以继续执行,不会被阻塞; -
当
Future
中的结果返回时,如果注册了
then
结果回调,
onValue
回调会拿到成功的返回值; -
当
Future
中出现执行异常时,如果注册了
catchError
失败回调,
onError
回调会拿到失败的异常信息和错误栈 。
我们通过一个简单的例子,进一步了解
Future
:
// test_isolate.dart
void testFuture() async{
print_cjf('testFuture start');
Future f1 = new Future(() {
print_cjf('future 1-1');
return "aaa";})
.then((value) {
print_cjf('future 1-2' + "---$value");
// throw 'bbb Error!'; //见 log2-1,log2-2
return "bbb"; //见 log1
})
.then((value) => print_cjf('future 1-3' + "---$value") )
.catchError((err, stackTrace) {
print_cjf('Caught $err' +"---$stackTrace");
return "ccc";
}, test: (err) {
print_cjf('Test $err');
return true; //见 log2-1。test return true:onError 会执行,后续 then 的 callback 也会被调用,不会抛出 Unhandled Exception
// return false; //见 log2-2。test reture false:onError 不会执行,后续 then 的 callback 不会被调用,且抛出[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: bbb Error!。
});
print_cjf('testFuture 1');
Future f2 = new Future(() => print_cjf('future 2-1'));
Future f3 = new Future(() => print_cjf('future 3-1'));
print_cjf('testFuture 2');
f3.then((value) => print_cjf('future 3-2' + "---$value"));
f2.then((value) => print_cjf('future 2-2' + "---$value"));
f1.then((value) => print_cjf('future 1-4' + "---$value"));
print_cjf('testFuture end');
}
//输出 log1
I/flutter (22807): 19:5:32:221:cjf---testFuture start //同步执行
I/flutter (22807): 19:5:32:222:cjf---testFuture 1 //同步执行
I/flutter (22807): 19:5:32:223:cjf---testFuture 2 //同步执行
I/flutter (22807): 19:5:32:223:cjf---testFuture end //同步执行
I/flutter (22807): 19:5:32:225:cjf---future 1-1 //异步执行
I/flutter (22807): 19:5:32:226:cjf---future 1-2---aaa //第一步返回的结果
I/flutter (22807): 19:5:32:228:cjf---future 1-3---bbb //第二步返回的结果
I/flutter (22807): 19:5:32:229:cjf---future 1-4---null //then 后调用,却先执行。没有异常,第三步没有返回,默认为 null
I/flutter (22807): 19:5:32:231:cjf---future 2-1
I/flutter (22807): 19:5:32:233:cjf---future 2-2---null //then 后调用,却先执行
I/flutter (22807): 19:5:32:235:cjf---future 3-1
I/flutter (22807): 19:5:32:236:cjf---future 3-2---null //then 先调用,后先执行
//输出 log2-1
I/flutter (22807): 19:6:58:613:cjf---testFuture start
I/flutter (22807): 19:6:58:615:cjf---testFuture 1
I/flutter (22807): 19:6:58:615:cjf---testFuture 2
I/flutter (22807): 19:6:58:615:cjf---testFuture end
I/flutter (22807): 19:6:58:617:cjf---future 1-1
I/flutter (22807): 19:6:58:618:cjf---future 1-2---aaa
I/flutter (22807): 19:6:58:619:cjf---Test bbb Error! //test 返回 ture,onError 会执行
I/flutter (22807): 19:6:58:621:cjf---Caught bbb Error!---#0 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:269:9)
I/flutter (22807): <asynchronous suspension>
I/flutter (22807): #1 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:276:20)
I/flutter (22807): <asynchronous suspension>
I/flutter (22807): 19:6:58:621:cjf---future 1-4---ccc //onError 捕获异常后,返回结果作为下一步 then 的入参
I/flutter (22807): 19:6:58:622:cjf---future 2-1
I/flutter (22807): 19:6:58:623:cjf---future 2-2---null
I/flutter (22807): 19:6:58:624:cjf---future 3-1
I/flutter (22807): 19:6:58:625:cjf---future 3-2---null
//输出 log2-2
I/flutter (22807): 19:7:48:994:cjf---testFuture start
I/flutter (22807): 19:7:48:996:cjf---testFuture 1
I/flutter (22807): 19:7:48:997:cjf---testFuture 2
I/flutter (22807): 19:7:48:998:cjf---testFuture end
I/flutter (22807): 19:7:49:0:cjf---future 1-1
I/flutter (22807): 19:7:49:2:cjf---future 1-2---aaa
I/flutter (22807): 19:7:49:4:cjf---Test bbb Error! //test 返回 false,onError 不会执行,后续的 then 回调也不会被执行,"future 1-4"不会被打印出来
E/flutter (22807): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: bbb Error!
E/flutter (22807): #0 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:269:9)
E/flutter (22807): <asynchronous suspension>
E/flutter (22807): #1 testFuture.<anonymous closure> (package:supermarie/testPages/test_isolate.dart:276:20)
E/flutter (22807): <asynchronous suspension>
E/flutter (22807):
I/flutter (22807): 19:7:49:7:cjf---future 2-1
I/flutter (22807): 19:7:49:8:cjf---future 2-2---null
I/flutter (22807): 19:7:49:10:cjf---future 3-1
I/flutter (22807): 19:7:49:12:cjf---future 3-2---null
从例子,我们可以看到:
-
所有
Future
创建的任务都是异步执行的,即创建时机和运行时机不同。上例的所有
log
中,
"future 1-1"
都在
"testFuture end"
之后执行; -
future
创建异步任务的次序,就是其加入到
Event queue
事件队列的次序,也是被异步执行的次序。输出
log
可以看到
"future1-1"
,
"future2-1"
,
"future3-1"
都是按照创建次序执行; -
future
通过
then
来获取异步结果,
then
支持链式调用。
then
注册的
onValue
结果回调,会在异步结果返回后立即执行,且后面的
then
的入参即是前面
then
回调的返回值。输出
log1
中
"future1-2---aaa"
打印了
"future1-1"
中的返回值,而
"future1-3---bbb"
打印了
"future1-2"
中的返回值; -
future
注册
then
结果回调的时机,不影响其被回调的时机。见
log1
,
"future 1-4"
,
"future 2-2"
,
"future 3-2"
的执行次序,和其调用次序没有关系,只和其依赖的
future
的创建次序有关; -
Future
通过
catchError
注册的
onError
回调,受
test
回调的返回值影响,我们对比
log2-1
和
log2-2
看:
-
如果
test
回调返回
true
,异常会被
onError
处理,并返回值作为下一步
then
的入参; -
如果
test
回调返回
false
,异常不会被
onError
处理,还会导致后续
Then
回调不被执行,如
"future 1-4"
不会被执行到。
Future
的链式调用,很好的解决了调用依赖导致的回调嵌套问题,我们以
Kotlin
中的登入流程为例做对比,源码如下:
// TestCallbackActivity.kt
class TestCallbackActivity extends BaseActivity {
// 登入
fun login(name:String, pwd:String, onSuccess:(uid:String) -> Unit, onError:() -> Unit):Unit {}
// 获取用户信息, 用户首选 tab
fun getUserInfo(uid:String, onSuccess:(UserBean) -> Unit, onError:() -> Unit):Unit {}
// 显示用户信息
fun showUserInfo(user:UserBean):Unit {}
// 获取用户首选 tab 动态流
fun getFeedListByTab(tab:Int, onSuccess:(feedList:List<NewFeedBean>) -> Unit, onError:() -> Unit):Unit { }
// 显示用户首选 tab 动态流
fun showFeedList(feedList:List<NewFeedBean>):Unit {}
// 获取用户第一个动态音乐
fun getBackgroudMusic(mid:String, onSuccess:(playUrl:String) -> Unit):Unit {}
// 播放用户动态音乐
fun playMusic(playUrl:String):Unit {}
override fun onResume() {
login("name", "pwd", onSuccess = {
//第一层回调,登入成功
getUserInfo(it, onSuccess = {
//第二层回调,获取用户信息成功
showUserInfo(it)
getFeedListByTab(it.mainTab, onSuccess = {
//第三层回调,获取用户主页 feed 流成功
showFeedList(it)
getBackgroudMusic(it[0].mid){
//第四层回调,获取 tab 流默认背景音乐成功
playMusic(it)
}
},
onError = {})},
onError = {})},
onError = {})
}
}
因为登入,获取用户信息,获取默认主页
feed
流,和主获取页流背景音乐这几个流程是有强依赖关系的,后面的步骤依赖前一个步骤返回的参数,导致每多一个步骤,就会多一层回调嵌套,非常影响可读性。
同样的逻辑,我们再通过
Flutter
的
Future
实现,见如下源码。
// testFutureCallback.dart
// 登入
Future<String> login(String name, String pwd) {}
// 获取用户信息, 用户首选 tab
Future<UserBean> getUserInfo(String uid) {}
// 显示用户信息
void showUserInfo(UserBean user) {}
// 获取用户首选 tab 动态流
Future<List<NewFeedBean>> getFeedListByTab(Int tab) {}
// 显示用户首选 tab 动态流
void showFeedList(List<NewFeedBean> feedList) {}
// 获取用户第一个动态音乐
Future<String> getBackgroudMusic(String mid) {}
// 播放用户动态音乐
void playMusic(String playUrl) {}
void testFuture2() async{
new Future(() {
return login("name", "pwd");
}).then((uid) {
return getUserInfo(uid);
}).then((user) {
showUserInfo(user);
return getFeedListByTab(user.mainTab);
}).then((feedList) {
showFeedList(feedList)
return getBackgroudMusic(feedList[0].mid)
}).then((playUrl) {
playMusic(playUrl)
}).catchError((err, stackTrace) {
// 根据 err 信息,处理异常
});
}
可以看到,
Future
通过
then
明确代码块执行的依赖关系,不但消除了多层回调嵌套,也简化了方法的定义和错误处理。当然
Kotlin
中也可以通过协程等方式解决回调嵌套问题。
4.3 async和await
在
Dart
中还可以通过
async
和
await
实现异步操作。
async
表示开启一个异步操作,可以返回一个
Future
结果。如果没有返回值,则默认返回一个
Future<void>
。
async
、
await
本质上就是
Dart
对异步操作的一个语法糖,可以减少异步调用的嵌套调用。这个语法是在
ES7
标准中推出的,
Dart
中的设计和
JS
相同,是同步风格的异步实现。
我们通过数据加载的示例,来进一步了解
async
,
await
操作:
class _DemoPageState extends State<DemoPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
print("initState end");
}
Future<String> loadData() async {
print("loadData start");
Response response = await getNetData(); //位置 1
setState(() {
widgets = json.decode(response.body); //位置 2
});
return response.body;
}
Future<Response> getNetData() async {
String requestURL = 'https://hy.sohu.com/testGetStringList1';
Client client = Client(); //位置 3
Future<Response> response = client.get(requestURL); //io 异步,后续章节会详细分析
return response; //位置 4
}
}
在代码示例中,执行到
loadData
方法时,会同步进入方法内部进行执行,当执行到位置 1 的
await
时,就会停止
loadData
方法内后续的代码的执行,返回到外部调用者
initState
继续执行后面的
print
语句。当位置 1 的
await
有返回后,会从位置 1 处继续执行
response
的赋值操作及后续的
setState
位置 2 的语句。
那么,
await
是怎么做到阻塞当前方法,却不阻塞调用者继续运行的呢?以及,
getNetData
方法中,位置 3 处的代码是被同步执行的,还是被异步执行的呢?
先来分析下第一个问题,我们知道在程序执行过程中,离开当前的调用位置有两种方式:
-
执行
return
返回,当前函数在调用栈中的局部变量、形参等状态会被销毁; -
继续调用其他函数,需要保先存当前函数的变量和执行位置,其它函数调用返回后再恢复变量继续执行。保存变量一般有 2 种方式:
-
一种,会将变量继续保存在栈区,在回到指针指向的离开位置时,会继续从栈中取出变量使用;
-
另一种,则在执行当前函数时就将变量直接分配到堆区,当再次回到当前位置时,还会继续从堆区中获取变量。
async
,
await
就属于这种方式。
在
Flutter
中,
async
函数也有自己的上下文环境。当执行到
await
时,会保存当前的上下文,并将当前位置标记为待处理任务,并将待处理任务放入当前
Isolate
的
event
队列中。在每个事件循环时询问这个任务是否满足执行条件,如果需要进行处理,就恢复上下文,从上次离开的位置继续执行。
所以说,
await
并没有开启新的
Isolate
,只是把
await
后的代码封装成待处理任务,放到当前
Isolate
的消息队列中,然后继续运行当前任务。因此
await
做到了不阻塞调用者的执行。
第二个问题,我们通过如下示例进一步说明:
// test_isolate.dart
void testAwait(){ // 测试 await 中断效果
print_cjf('testAwait start');
fun1();
fun2();//async 方法,会 await 一个异步结果
fun3();
fun4();//async 方法,但是并没有 await 异步结果
print_cjf('testAwait end');
}
void fun1(){
print_cjf('fun1');
}
void fun2() async{ //声明异步方法 使用 async
print_cjf('fun2 start');
await Future.delayed(Duration(seconds: 2)); //使用 await,会异步等待结果返回
print_cjf('fun2 end');
}
void fun3(){
print_cjf('fun3');
}
void fun4() async{ //声明异步方法 使用 async
print_cjf('fun4 start');
Future.delayed(Duration(seconds: 5)); //没有使用 await, 不会等待结果返回
print_cjf('fun4 end');
}
// 输出 log
I/flutter (24288): 16:34:25:354:cjf---testAwait start
I/flutter (24288): 16:34:25:354:cjf---fun1
I/flutter (24288): 16:34:25:354:cjf---fun2 start // await 前的代码被同步执行了
I/flutter (24288): 16:34:25:355:cjf---fun3 // 不需要等待 fun2 方法执行完,就被同步执行了
I/flutter (24288): 16:34:25:355:cjf---fun4 start // 被同步执行了
I/flutter (24288): 16:34:25:355:cjf---fun4 end// 同步执行。并没有在 fun4 start 后等 5s 再输出。 但是在 5s 内程序不会退出
I/flutter (24288): 16:34:25:355:cjf---testAwait end //fun1,fun2,fun3,fun4 并没有阻塞 testAwait 后续代码的执行
I/flutter (24288): 16:34:27:359:cjf---fun2 end//fun2 start 后, 等 2 秒输出 end
这个示例中,我们通过交替调用
async
和非
async
函数,来理解同步调用。我们对比观察
fun2
和
fun4
中
log
的输出情况,可以发现:
-
fun2
中,
await
前的代码
"fun2 start"
在
"fun3"
前执行,所以这行代码是被同步执行的; -
fun2
中,
await Future.delayed
,阻塞了
"fun2 end"
的同步执行,它会等待
delay
2 秒后才被执行,所以最后被执行; -
fun4
中,
Future.delayed
没有使用
await
,函数执行没有被阻塞,
"fun4 end"
在
"testAwait end"
前执行,所以是被同步执行的,即
Future.delayed
创建的异步任务不会打断当前函数执行。
这个示例中,
Future.delayed
函数本身是同步执行的,它创建一个异步任务,放入当前
Isolate
的
event
队列后返回。而
await Future.delayed
则需要等待创建的异步任务执行完成后,再恢复当前函数后续代码的执行。
通过
log
分析可知:
await
关键字,把同一个函数分割为同步执行部分和异步执行两部分
await
关键字之前的代码和调用者在同一个任务中同步执行。而没有
await
调用的
async
函数,代码都是同步执行的。
所以在
_DemoPageState
例子中,
getNetData
函数位置 3 的代码会和调用者在同一个任务中被同步执行。而
loadData
函数位置 2 的代码属于待处理任务,会被异步执行。
在 4.1 章节的
testMicroTask
微任务例子中,我们也可以看到
funDelay
异步方法
await
前的
"funDelay start"
代码是被同步执行的。
对比
testFuture
例子中
Future
创建和
then
回调的例子,从
log
中也可以知道,
Future
中创建的异步任务,都是被异步执行的。
await
的使用效果和
Android Kotlin
协程很类似,
launch
和
async
只是启动协程
job
,
job await
要等待协程结果,才会真正阻塞当前函数后续代码的执行,但是它不阻塞调用线程中其它代码的运行。虽然
Kotlin
协程的异步原理和
Flutter
不同,但通过单线程异步支持大量并发操作的设计思想类同,可以一起对比学习。
Future
和
async
,
await
两种异步方式,在我们的项目中都有频繁使用,各有优点和适用场景,我们可以通过例子可以更直观的说明:
main( ){
new Future
.then(funA())
.then(funB()); // then 明确表现出了 funB 依赖 funA
}
main( ) async {
await funA( );
await funB(); // funB 依赖 funA 的关系不明显
}
Future
能明确方法执行的依赖次序,其它开发者不容易破坏这层依赖逻辑。而
await
没有明确体现方法之间的依赖关系。对于没有通过返回值直接体现依赖的情况下,其他开发者不容易理解到这层依赖,会导致后续代码难以维护。
而
await
的风格,代码更简洁美观,如果没有依赖关系,更推荐这种风格。
4.4 IO异步
在前面章节中,
testIsolate
,
testFuture
和
testAwait
例子中,这些函数和新建异步任务都是在
Root Isolate
中运行的。以及
_DemoPageState
例子中加载网络数据的
getNetData
耗时方法也是在
Root Isolate
中运行的。
但是
Task Runner
章节中明确
UI Runner
负责界面的更新,也运行在
Root Isolate
中。为了避免界面卡顿,必然不能在
Root Isolate
的异步任务中执行耗时任务。
那么像网络请求,文件读取,海量计算,图片编解码等耗时函数的调用,是不是都需要创建新的
Isolate
来处理呢?
我们在实际项目中,会大量使用网络相关耗时
IO
操作。下面通过网络请求过程为例,进一步分析
IO
异步:
//home_page.dart
class KKHomePageState extends BaseThemeState<KKHomePage> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
..........
@override
void initState() {
super.initState();
.......
if (_login) {
print_cjf("h02---" + Isolate.current.debugName.toString());
_refresh(); //准备刷新数据
print_cjf("h022---" + Isolate.current.debugName.toString());
}
}
......
Future<void> _refresh() async {
print_cjf("h5---" + Isolate.current.debugName.toString());
await _viewModel.getList(15, 1); // 获取网络数据,等待数据结果。虽然没有直接使用返回值,但是此方法后续的代码中依赖网络数据,所以此处必须 await。但如果后续代码是通过监听的方式来使用网络数据,此处可去掉 await。
print_cjf("h6---" + Isolate.current.debugName.toString());
homeAllList.currentState?.resetDataAndRefresh();
........
}
}
// subscribe_list_vm.dart
Future<void> getList(int? limit, int? status) async {
........
print_cjf("h7---" + Isolate.current.debugName.toString());
await NetManager.getSubjectSubscribeClient()
.getMySubscribed(BaseRequestParams().makeSignMap(urlMap: requestMap)) //获取用户订阅的主题
.then((value) {
print_cjf("h8---" + Isolate.current.debugName.toString());
......
}
........
}
// log 输出:
I/flutter (13129): 15:32:23:235:cjf---h02---main // main_page 中的 initstate,调用_refresh 之前。同步执行。
I/flutter (13129): 15:32:23:236:cjf---h5---main // 进入 _refresh() async {}方法,调用_viewModel.getList 之前。同步执行。
I/flutter (13129): 15:32:23:238:cjf---h7---main // 同步执行。await _viewModel.getList(15, 1); 进入到 subscribe_list_vm 中的 getList 方法。同步执行。
I/flutter (13129): 15:32:23:239:cjf---h022---main // 同步执行。main_page 中的 initstate,调用_refresh 之后。同步执行。
I/flutter (13129): 15:32:23:742:cjf---h8---main // await NetManager.getSubjectSubscribeClient().getMySubscribed 方法返回。异步步执行。
I/flutter (13129): 15:32:23:760:cjf---h6---main // 进入 _refresh() async {}方法,调用_viewModel.getList 之后。异步执行。
从
log
可以看出网络请求
getList async
方法的代码也是在 Root Isolate 执行的,并没有启动新的
Isolate
。
await
等待
aysnc
方法结果时,会产生
Isolate
内的异步调度,即把待处理任务
h6
和
h8
放到当前
Isolate
的消息队列中,然后继续运行当前任务打印
"h022---main"
。
那么
NetManager.getSubjectSubscribeClient
通过
retrofit
获取网络数据的耗时
IO
也是在
Root Isolate
中执行的吗?
我们通过网络限速,使得获取网络数据的接口 5s 后返回结果。验证发现
KKHomePage
在这 5s 中可以流畅地进行操作,且能打印出
Root Isolate
中其它异步任务执行的
log
,这说明网络耗时
IO
没有在
Root Isolate
中执行,这是什么原因呢?
这是因为网络请求过程中,当运行到系统
IO
接口时,会将
IO
任务交给系统处理(属于系统的线程),并将异步任务加入到事件队列,然后
Root Isolate
回到事件循环,获取事件队列中的下一个任务进行处理。
Flutter
文档中,没有明确哪些接口属于系统
IO
接口,但是通过
Dart SDK
的源码跟踪,我们可以发现,网络请求
HttpClient
最后的
IO
操作是在
dart
虚拟机中的
native socket
中实现的,而
socket
中使用了线程池来管理线程,并把
IO Task
分配到各子线程中运行。
所以,对于有系统
IO
操作的耗时任务,如网络请求,文件读取等,可以使用
await
,等待耗时
IO
操作的异步结果。
而执行大量高
CPU
的运算类耗时任务,如耗时计算,编解码等,即便使用
await
,但所有代码都运行在
Root Isolate
中,所以仍然会导致
Root Isolate
没法及时处理其他异步任务,从而导致
UI
卡顿。所以官方推荐高
CPU
耗时任务,应该使用新的
Isolate
去执行,如前面章节的
testCompute
例子所示。
通过上面的分析,我们就可以很好地理解,为什么官方只说
Isolate
不支持高
CPU
操作,而没有说不支持耗时
IO
操作了。
开辟新的
Isolate
的成本比较高,所以对于不太耗时的任务,还是建议用
Future
或
await
的方式。那么,什么样的任务算高
CPU
的耗时任务呢?这个没有绝对标准,建议毫秒级的任务用
Future
或
await
,而百毫秒级的任务,比如图片处理,数据加密等,开启新的
Isolate
。
4.5 Js异步
Flutter
中的线程异步和浏览器中异步原理是一样的,而且
async
,
await
语法糖也是在
ES7
标准中推出的,通过对比,我们可以进一步对
Flutter
的异步加深了解。
在
JavaScript
的世界中,所有代码都是单线程执行的,即通常情况下,
Js
代码不必考虑多线程。
JS
中,异步的目的是为了提高
CPU
的执行效率,提高用户体验。它和同步一样,运行在同一线程中。它和同步的差别在于,同一流水线上各个代码片段的执行顺序不同。
下面我们通过示例来看一下
JS
中的异步:
// test.js
async function fun1() {
var vConsole = new VConsole();
console.log('cjf--- fun1---1')
var a = 123
console.log('cjf--- fun1---2')
var b = await fun2() //等待 fun2 的异步结果
console.log('cjf--- fun1---3')
var c = b
console.log('cjf--- fun1---4')
return 1;
}
async function fun2() {
console.log('cjf--- fun2---1')
var a = 123
console.log('cjf--- fun2---2')
var b = await fun3() //等待 fun3 的异步结果
console.log('cjf--- fun2---3')
var c = b
console.log('cjf--- fun2---4')
}
async function fun3() {
console.log('cjf--- fun3---1')
var a = 123
console.log('cjf--- fun3---2')
var b = await makeRequest() //等待网络请求的异步结果
console.log('cjf--- fun3---3')
var c = b
console.log('cjf--- fun4---4')
return 3;
}
function makeRequest() {
console.log('cjf--- makeRequest---1')
httpRequest = new XMLHttpRequest();
if (!httpRequest) {
alert('Giving up :( Cannot create an XMLHTTP instance');
return false;
}
httpRequest.onreadystatechange = alertContents; // 注册异步回调
httpRequest.open('GET', 'test.html'); // 耗时 IO 接口,会切换到引擎新线程
console.log('cjf--- makeRequest---2')
httpRequest.send(); // 耗时 IO 接口,会切换到引擎新线程
console.log('cjf--- makeRequest---3')
return 4
}
function alertContents() { // 数据 ready 回调
console.log('cjf--- alertContents---1---' + httpRequest.readyState)
if (httpRequest.readyState === XMLHttpRequest.DONE) {
console.log('cjf--- alertContents---2')
if (httpRequest.status === 200) {
alert(httpRequest.responseText);
console.log('cjf--- alertContents---3---' + httpRequest.responseText)
} else {
alert('There was a problem with the request.');
console.log('cjf--- alertContents---4---a problem with the request.')
}
}
}
fun1() //调用方法。
// log 输出
cjf--- fun1---1
cjf--- fun1---2
cjf--- fun2---1
cjf--- fun2---2
cjf--- fun3---1
cjf--- fun3---2 //同步执行
cjf--- makeRequest---1 //同步执行
cjf--- alertContents---1---1 //httpRequest.open 会回调 alertContents
cjf--- makeRequest---2 //异步执行
cjf--- makeRequest---3 //异步执行
cjf--- fun3---3 //异步执行
cjf--- fun3---4 //异步执行
cjf--- fun2---3 //异步执行
cjf--- fun2---4 //异步执行
cjf--- fun1---3 //异步执行
cjf--- fun1---4 //异步执行
cjf--- alertContents---1---4 //httpRequest.send 会异步回调 alertContents
cjf--- alertContents---2 //httpRequest.send 会异步回调 alertContents
cjf--- alertContents---4---a problem with the request. //httpRequest.send 会异步回调 alertContents
从上述例子可以看出,
Func1
,
Func2
和
Func3
中的
a
赋值操作都是同步执行的,
b
,
c
的赋值操作是异步执行的。
在浏览器运行
await
时, 会一层一层运行到
游览器引擎新建线程接口
(比如
Ajax
请求接口,
XMLHttpRequest.send
),调用系统
IO
接口前的代码片段是被同步执行的,之后的代码片段则需要等到异步结果返回后,才能继续执行。
所以
await
实际上并不是异步执行的 “分界线”,即
await
前面的代码同步执行,
await
后面的代码异步执行。因为
await
后面的异步函数代码中的部分内容,也可能被同步执行,代码会一直运行到
系统
IO
接口
处,才算到了分界线。但是从执行的结果来看,
await
确实达到了 “阻塞等待” 的效果。
在使用时,我们并不关心异步函数中的部分代码被同步执行了还是被异步执行了,我们关心的是
await
的异步数据必须返回,才能再继续执行下面的代码。但对原理的深入理解,可以提高我们对系统设计思想的运用能力。
小结
通过上面的学习,我们对
Flutter
中的线程模型和单线程异步原理有了更深入的理解,开发时也能更好更安全地运用
Flutter
的
Isolate
,
Future
和
await
。
我们最后总结下文章的核心内容:
-
Isolate
是
Dart
平台对线程的实现方案。和普通
Thread
不同,
Isolate
拥有独立的内存,由线程和独立内存构成。
Isolate
线程之间并不存在资源抢夺的问题,所以也不需要线程同步机制; -
简单耗时任务,可以使用简版
Isolate
:
compute
; -
Isolate
通过事件循环和消息队列来实现单线程异步并发,这比多线程异步并发要轻便; -
可以通过
Future
进行单线程异步并。
Future
异步的特点是提供了链式调用,可以解决回调地狱,异常捕获和代码执行依赖等问题; -
也可以通过
async
和
await
实现单线程异步并发,它是同步风格的异步操作,比
Future
更简洁美观; -
Root Isolate
支持
IO
耗时操作,但不支持高
CPU
耗时操作。