网站推广app软件下载,苏州网站搭建公司,佛山做外贸网站信息,微信公众号和微网站本文字数#xff1a;#xff1a;36130字 预计阅读时间#xff1a;91分钟 在Android应用中#xff0c; 用户时常会遇到界面卡顿的情况#xff0c;非常影响用户的体验。作为Android开发肯定都知道#xff1a;应用在主线程里做了大量的耗时操作(例如文件读写#xff0c; 数… 本文字数36130字 预计阅读时间91分钟 在Android应用中 用户时常会遇到界面卡顿的情况非常影响用户的体验。作为Android开发肯定都知道应用在主线程里做了大量的耗时操作(例如文件读写 数据库读写网络访问图片编解码等)就会导致UI不能及时刷新出现跳帧现象。如果应用主线程不能及时处理用户的输入事件或广播消息系统甚至会直接弹出著名的ANR对话框提示用户杀死应用。 在Flutter应用中如果出现界面卡顿它的原因也是如此吗 我们带着这些疑问一起来搞清楚Flutter的线程模型和异步原理并找到问题的答案。 一、Flutter系统结构 首先我们熟悉下Flutter官方提供的系统结构图 整体框架是采用分层设计的自上而下分别是Framework、Engine、Embedder。 Framework基于Dart语言构建的Framework包括了各种UI组件动画和手势识别等并将所有的设计通过Widgets小组件层进行抽象封装。所以在Flutter中一切都是 WidgetEngine基于C/C构建的引擎包括了Skia、Dart和Text等实现了Flutter渲染引擎文字排版事件处理和Dart运行时等功能。Skia 和 Text 为上层提供调用底层渲染和排版的功能Dart运行时提供了调用Dart和渲染引擎的能力Embedder嵌入层是操作系统适配层会将Flutter嵌入到各个平台上。嵌入层负责适配原生平台插件、线程管理、渲染Surface设置等。 从架构图中可以看到Embedder负责线程的创建和管理并且提供Task Runner给Engine使用。Engine虽然自己并不创建和管理线程但是它通过 Dart VMDart Runtime Mgmt提供Isolate给Framework和应用层进行多线程创建。 二、Task Runner 接下来我们继续了解Embeder提供的四个Task RunnerPlatform RunnerUI RunnerGPU RunnerIO 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资源比如平台FramebufferSurfaceTexture和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 BaseThemeStateKKMainTabPagewith 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 创建新 isolatetestIsolate 方法中断执行需等待异步结果回来后恢复执行。
I/flutter ( 4910): 11:25:56:590:cjf---initState 3---main //KKMainTabPageState 的 initStateawait 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---mainbuild 4---main,initState 1---maintestIsolate 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.1testIsolate 5.2testIsolate 5.3testIsolate 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为我们提供了一个简版的Isolatecompute。 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---count617283945 compute方法是Flutter foundation包中的顶层方法它对Isolate.spawn及sendPort消息发送做了封装返回一个异步结FutureR。 通过封装后的接口我们只需简单调用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 ---counter1, i1, intObject1//MemoryTask Isolate 中的顶层
变量发生改变
I/flutter ( 4910): 12:58:23:96:cjf---testIsolateMemory ---datacounter1, i1, intObject1
I/flutter ( 4910): 12:58:23:97:cjf---testIsolateMemory ---i0, intObject0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:24:87:cjf---MemoryTask ---counter2, i2, intObject2
I/flutter ( 4910): 12:58:24:89:cjf---testIsolateMemory ---datacounter2, i2, intObject2
I/flutter ( 4910): 12:58:24:90:cjf---testIsolateMemory ---i0, intObject0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:25:89:cjf---MemoryTask ---counter3, i3, intObject3
I/flutter ( 4910): 12:58:25:90:cjf---testIsolateMemory ---datacounter3, i3, intObject3
I/flutter ( 4910): 12:58:25:91:cjf---testIsolateMemory ---i0, intObject0 //Root Isolate 中的顶变量没有改变
I/flutter ( 4910): 12:58:26:89:cjf---MemoryTask ---counter4, i4, intObject4
I/flutter ( 4910): 12:58:26:90:cjf---testIsolateMemory ---datacounter4, i4, intObject4
I/flutter ( 4910): 12:58:26:91:cjf---testIsolateMemory ---i0, intObject0 //Root Isolate 中的顶变量没有改变I/flutter ( 4910): 12:58:26:621:cjf---testIsolateMemory ---delayed:i1, intObject1 //Root Isolate 中给顶层变量加1I/flutter ( 4910): 12:58:27:88:cjf---MemoryTask ---counter5, i5, intObject5
I/flutter ( 4910): 12:58:27:90:cjf---testIsolateMemory ---datacounter5, i5, intObject5
I/flutter ( 4910): 12:58:27:90:cjf---testIsolateMemory ---i1, intObject1 //Root Isolate 中的顶层变量发生改变I/flutter ( 4910): 12:58:28:89:cjf---MemoryTask ---counter6, i6, intObject6
I/flutter ( 4910): 12:58:28:90:cjf---testIsolateMemory ---datacounter6, i6, intObject6
I/flutter ( 4910): 12:58:28:91:cjf---testIsolateMemory ---i1, intObject1
I/flutter ( 4910): 12:58:29:87:cjf---MemoryTask ---counter7, i7, intObject7
I/flutter ( 4910): 12:58:29:89:cjf---testIsolateMemory ---datacounter7, i7, intObject7
I/flutter ( 4910): 12:58:29:90:cjf---testIsolateMemory ---i1, intObject1
I/flutter ( 4910): 12:58:30:89:cjf---MemoryTask ---counter8, i8, intObject8
I/flutter ( 4910): 12:58:30:91:cjf---testIsolateMemory ---datacounter8, i8, intObject8
I/flutter ( 4910): 12:58:30:92:cjf---testIsolateMemory ---i1, intObject1
I/flutter ( 4910): 12:58:31:89:cjf---MemoryTask ---counter9, i9, intObject9
I/flutter ( 4910): 12:58:31:91:cjf---testIsolateMemory ---datacounter9, i9, intObject9
I/flutter ( 4910): 12:58:31:91:cjf---testIsolateMemory ---i1, intObject1
I/flutter ( 4910): 12:58:32:89:cjf---MemoryTask ---counter10, i10, intObject10
I/flutter ( 4910): 12:58:32:90:cjf---testIsolateMemory ---datacounter10, i10, intObject10
I/flutter ( 4910): 12:58:32:91:cjf---testIsolateMemory ---i1, intObject1I/flutter ( 4910): 12:58:32:91:cjf---testIsolateMemory ---dataMemoryTask ---Isolate exit with last message to sendPort
I/flutter ( 4910): 12:58:32:92:cjf---testIsolateMemory ---i1, intObject1 我们从输出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 ThreadLocalLooper sThreadLocal new ThreadLocalLooper();......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); // 异步执行
}Futureint 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还是log2funDelay 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中进行异步调用一般需要用到三个关键词Futureasyncawait。其中async和await需要一起使用。 FutureFuture表示异步操作的返回结果可以通过then处理返回结果。和Java中的Future功能差别巨大注意区别使用async标记某个方法为异步方法在声明方法的时候使用。其返回值是Future类型await表示等待某个异步方法的结果一般调用耗时异步方法时使用。 下面我们继续深入Futureasyncawait的使用和原理学习。 4.2 Future 在Dart中可以通过Future进行异步操作Future异步的特点是提供了链式调用可以解决回调地狱异常捕获和代码执行依赖等问题。 FutureT表示一个指定类型的异步操作结果如果不需要结果可以使用Futurevoid。我们可以直接使用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-1log2-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 trueonError 会执行后续 then 的 callback 也会被调用不会抛出 Unhandled Exception// return false; //见 log2-2。test reture falseonError 不会执行后续 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 返回 tureonError 会执行
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 返回 falseonError 不会执行后续的 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:ListNewFeedBean) - Unit, onError:() - Unit):Unit { }// 显示用户首选 tab 动态流fun showFeedList(feedList:ListNewFeedBean):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// 登入
FutureString login(String name, String pwd) {}
// 获取用户信息, 用户首选 tab
FutureUserBean getUserInfo(String uid) {}
// 显示用户信息
void showUserInfo(UserBean user) {}
// 获取用户首选 tab 动态流
FutureListNewFeedBean getFeedListByTab(Int tab) {}
// 显示用户首选 tab 动态流
void showFeedList(ListNewFeedBean feedList) {}
// 获取用户第一个动态音乐
FutureString 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结果。如果没有返回值则默认返回一个Futurevoid。 async、await本质上就是Dart对异步操作的一个语法糖可以减少异步调用的嵌套调用。这个语法是在ES7标准中推出的,Dart中的设计和JS相同是同步风格的异步实现。 我们通过数据加载的示例来进一步了解async,await操作 class _DemoPageState extends StateDemoPage {List widgets [];overridevoid initState() {super.initState();loadData();print(initState end);}FutureString loadData() async {print(loadData start);Response response await getNetData(); //位置 1setState(() {widgets json.decode(response.body); //位置 2});return response.body;}FutureResponse getNetData() async {String requestURL https://hy.sohu.com/testGetStringList1;Client client Client(); //位置 3FutureResponse 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异步 在前面章节中testIsolatetestFuture和testAwait例子中这些函数和新建异步任务都是在Root Isolate中运行的。以及_DemoPageState例子中加载网络数据的getNetData耗时方法也是在Root Isolate中运行的。 但是Task Runner章节中明确UI Runner负责界面的更新也运行在Root Isolate中。为了避免界面卡顿必然不能在Root Isolate的异步任务中执行耗时任务。 那么像网络请求文件读取海量计算图片编解码等耗时函数的调用是不是都需要创建新的Isolate来处理呢 我们在实际项目中会大量使用网络相关耗时IO操作。下面通过网络请求过程为例进一步分析IO异步 //home_page.dart
class KKHomePageState extends BaseThemeStateKKHomePage with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {..........overridevoid initState() {super.initState();.......if (_login) {print_cjf(h02--- Isolate.current.debugName.toString());_refresh(); //准备刷新数据print_cjf(h022--- Isolate.current.debugName.toString());}}......Futurevoid _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
Futurevoid 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中的线程异步和浏览器中异步原理是一样的而且asyncawait语法糖也是在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 从上述例子可以看出Func1Func2和Func3中的a赋值操作都是同步执行的bc的赋值操作是异步执行的。 在浏览器运行await时 会一层一层运行到游览器引擎新建线程接口比如Ajax请求接口XMLHttpRequest.send调用系统IO接口前的代码片段是被同步执行的之后的代码片段则需要等到异步结果返回后才能继续执行。 所以await实际上并不是异步执行的 分界线即 await前面的代码同步执行await后面的代码异步执行。因为await后面的异步函数代码中的部分内容也可能被同步执行代码会一直运行到系统IO接口处才算到了分界线。但是从执行的结果来看await确实达到了 “阻塞等待” 的效果。 在使用时我们并不关心异步函数中的部分代码被同步执行了还是被异步执行了我们关心的是await的异步数据必须返回才能再继续执行下面的代码。但对原理的深入理解可以提高我们对系统设计思想的运用能力。 小结 通过上面的学习我们对Flutter中的线程模型和单线程异步原理有了更深入的理解开发时也能更好更安全地运用Flutter的IsolateFuture和await。 我们最后总结下文章的核心内容 Isolate是Dart平台对线程的实现方案。和普通Thread不同Isolate 拥有独立的内存由线程和独立内存构成。Isolate线程之间并不存在资源抢夺的问题所以也不需要线程同步机制简单耗时任务可以使用简版IsolatecomputeIsolate通过事件循环和消息队列来实现单线程异步并发这比多线程异步并发要轻便可以通过Future进行单线程异步并。Future异步的特点是提供了链式调用可以解决回调地狱异常捕获和代码执行依赖等问题也可以通过async和await实现单线程异步并发它是同步风格的异步操作比Future更简洁美观Root Isolate支持IO耗时操作但不支持高CPU耗时操作。