本文字数::36130字
预计阅读时间:91分钟
在Android
应用中, 用户时常会遇到界面卡顿的情况,非常影响用户的体验。作为Android
开发肯定都知道:应用在主线程里做了大量的耗时操作(例如文件读写, 数据库读写,网络访问,图片编解码等),就会导致UI
不能及时刷新,出现跳帧现象。如果应用主线程不能及时处理用户的输入事件或广播消息,系统甚至会直接弹出著名的ANR
对话框,提示用户杀死应用。
在Flutter
应用中,如果出现界面卡顿,它的原因也是如此吗?
我们带着这些疑问,一起来搞清楚Flutter
的线程模型和异步原理,并找到问题的答案。
一、Flutter系统结构
首先,我们熟悉下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 Runner
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 Runner
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
主线程中的代码。
Flutter
应用默认在单线程中运行Dart
代码,如果不开启新的线程,所有Dart
代码都在Root Isolate
主线程中运行。
我们可以通过Isolate
的创建和Port
数据通信的例子来进一步了解代码执行的线程情况:
// kiki_main_tab_page.dart
class KKMainTabPageState extends BaseThemeState<KKMainTabPage>with WidgetsBindingObserver {// 这是摸鱼kik的主界面......@overridevoid initState() {super.initState();......//测试 Future 中代码的运行所属 IsolateFuture.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());}@overrideWidget 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 的发送端口传给 newIsolatevar 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, 并发送结束消息给 sendPort2Isolate.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 的 SendPort1I/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---mainI/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---aaaaaaI/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 sendPort2I/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 传过来的Stringprint_cjf('testIsolateMemory ---'+ "data===$msg");//打印 Root Isolate 中的变量值print_cjf('testIsolateMemory ---'+ "i=$intValue, intObject=${intObject.get()}");});//5s后,给顶层变量加1Future.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,给顶层变量和局部变量都加1Timer.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, 并发送结束消息给 sendPortIsolate.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 endI/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 中给顶层变量加1I/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=1I/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 2scheduleMicrotask(() =>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{ //声明异步方法, 必须使用 asyncprint_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 //第二层 futureI/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//第二层 microtaskI/flutter ( 9669): 17:58:35:97:cjf---funDelay end //属于第一层 future
I/flutter ( 9669): 17:58:35:97:cjf---testMicroTask end //属于第一层 futureI/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-2return "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 后调用,却先执行。没有异常,第三步没有返回,默认为 nullI/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 endI/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 endI/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 {}// 获取用户信息, 用户首选 tabfun 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 = [];@overridevoid initState() {super.initState();loadData();print("initState end");}Future<String> loadData() async {print("loadData start");Response response = await getNetData(); //位置 1setState(() {widgets = json.decode(response.body); //位置 2});return response.body;}Future<Response> getNetData() async {String requestURL = 'https://hy.sohu.com/testGetStringList1';Client client = Client(); //位置 3Future<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{ //声明异步方法 使用 asyncprint_cjf('fun2 start');await Future.delayed(Duration(seconds: 2)); //使用 await,会异步等待结果返回print_cjf('fun2 end');
}void fun3(){print_cjf('fun3');
}void fun4() async{ //声明异步方法 使用 asyncprint_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 {..........@overridevoid 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 = 123console.log('cjf--- fun1---2')var b = await fun2() //等待 fun2 的异步结果console.log('cjf--- fun1---3')var c = bconsole.log('cjf--- fun1---4')return 1;
}async function fun2() {console.log('cjf--- fun2---1')var a = 123console.log('cjf--- fun2---2')var b = await fun3() //等待 fun3 的异步结果console.log('cjf--- fun2---3')var c = bconsole.log('cjf--- fun2---4')
}async function fun3() {console.log('cjf--- fun3---1')var a = 123console.log('cjf--- fun3---2')var b = await makeRequest() //等待网络请求的异步结果console.log('cjf--- fun3---3')var c = bconsole.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
耗时操作。