Flutter中async与await异步编程原理分析
题记
—— 执剑天涯,从你的点滴积累开始,所及之处,必精益求精,即是折腾每一天。
** | 你可能需要 |
---|---|
CSDN | 网易云课堂教程 |
掘金 | EDU学院教程 |
知乎 | Flutter系列文章 |
在写过几篇异步编程的使用后,是时候于大家分享下Flutter异步编程的原理了。
Header1 | Header2 |
---|---|
Flutter延时任务、Flutter通过Future与Timer实现延时任务 | Flutter异步编程async与await的基本使用 |
Flutter异步编程async与await的基本使用 | Flutter异步加载FutureBuilder重绘解决方案 |
1异步编程基本概念
1.1 任务调度
先谈谈任务调度 ,大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,对于单核CPU来讲,并行执行两个任务,实际上是CPU在进行着快速的切换,对用户来讲感觉不到有切换停顿,就好比220V交流电灯光显示原理一样,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。
任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来,任务的停与执行切换,称之为任务调度。
1.2 进程
计算机的核心是CPU,它承担了所有的计算任务,而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,操作系统中运行着多个进程,每一个进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是应用程序运行的载体。
操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是资源分配的最小单位,也就是操作系统的最小单位。
1.3 线程
线程是进程中的概念,一个进程中可包含多个线程。
任务调度采用的是时间片轮转的抢占式调度方式,进程是任务调度的最小单位。
默认情况下,一般一个进程里只有一个线程,进程本身就是线程,所以线程可以被称为轻量级进程。
1.4 协程(Coroutines)
协程,是一种基于线程,但又比线程更加轻量级的存在,是线程中的概念,一个线程可以拥有多个协程。
在传统的J2EE体系中都是基于每个请求占用一个线程去完成完整的业务逻辑(包括事务)。所以系统的吞吐能力取决于每个线程的操作耗时。如果遇到很耗时的I/O行为,则整个系统的吞吐立刻下降,因为这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多其他的线程处于等待,空闲状态(等待前面的线程执行完才能执行),造成了资源应用不彻底。
最常见的例子就是同步阻塞的JDBC,在连接过程中线程根本没有利用CPU去做运算,而是处在等待状态,而另外过多的线程,也会带来更多的ContextSwitch(上下文切换)开销。
协程的出现,当出现长时间的I/O操作时,通过让出当前占用的任务通道,执行下一个任务的方式,通过在线程中实现调度,来消除ContextSwitch上的开销,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在IO上的性能瓶颈。从编程角度上看,协程的思想本质上就是控制流的主动让出(yield)和恢复(resume)机制。
2 Flutter 项目中的异步编程原理
如使用Flutter开发的APP安装在手机上,当点击APP图标启动时,手机操作系统会为当前APP创建一个进程,然后在Flutter项目中通过main函数启动Flutter构建的项目。
Dart是基于单线程模型的语言,所以在Flutter中我们一般的异步操作,实际上还是通过单线程通过调度任务优先级来实现的。
在Dart中的线程机制,称为isolate,在Flutter项目中, 运行中的 Flutter 程序由一个或多个 isolate 组成,默认情况下启动的Flutter项目,通过main函数启动就是创建了一个main isolate,后续会有专门一文来论述isolate的开发使用,在这里我们 main isolate 为Flutter的主线程,或者是UI线程。
2.1 Dart事件循环
单线程模型中主要就是在维护着一个事件循环(Event Loop) 与 两个队列(event queue和microtask queue)
当Flutter项目程序触发如点击事件、IO事件、网络事件时,它们就会被加入到eventLoop中,eventLoop一直在循环之中,当主线程发现事件队列不为空时发现,就会取出事件,并且执行。
microtask queue只处理在当前 isolate 中的任务,优先级高于event queue,好比机场里的某个VIP候机室,总是VIP用户先登机了,才开放公共排队入口,如果在event事件队列中插入microtask,当当前event执行完毕即可插队执行microtask事件,microtask queue队列的存在为Dart提供了给任务队列插队的解决方案。
当事件循环正在处理microtask事件时的时候,event queue会被堵塞。这时候app就无法进行UI绘制,响应鼠标事件和I/O等事件。
这两个任务队列中的任务切换就相当于是协程调度机制。
2.2 Future 概述
Future就是event,每一个被await标记的句柄也是一个event,timer创建的任务也是一个event,每创建一个Future就会把这个Future扔进event queue中排队。
使用async和await组合,即可向event queue中插入event实现异步操作。
Future最主要的功能就是提供了链式调用方式以及完整的一套处理异步任务的方法。
2.3 Future 的常用方法概述
Flutter提供了下面三个方法,让我们来注册回调,来监听处理Future异步信息的结果:
//处理完成时候的回调,一般都是成功回调
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
//处理失败的回调,比如throw一个error就会走到这里
Future<T> catchError(Function onError, {bool test(Object error)});
//Future.whenComplete总是在Future完成后调用,不管Future的结果是正确的还是错误的。
Future<T> whenComplete(FutureOr action());
Future 的构造方法,创建一个基本的Future
var f1 = Future(() {
print("1111");
});
print("33233");
//33233
//1111
创建一个指定返回值的Future
Future.value("Success").then((value) => (print('测试$value')));
创建一个延迟执行的 Future
//延迟三秒执行
Future.delayed(Duration(seconds: 3), () {
print("future delayed");
});
根据某个集合,创建一系列的Future,并且会按顺序执行这些Future
Future.forEach([1,2,3], (item) {
return Future.delayed(Duration(seconds: 2),() {
print(item);
});
});
//1
//2
//3
串行执行多个异步任务
var f1 = Future.delayed(Duration(seconds: 1),() => (1));
var f2 = Future.delayed(Duration(seconds: 2),() => (2));
var f3 = Future.delayed(Duration(seconds: 3),() => (3));
Future.wait([f1,f2,f3]).then((value) => print(value)).catchError(print);
在上述创建的异步任务都是添加到event队列中的任务,创建一个在microtask队列运行的future,microtask队列的优先级是比event队列高的。
Future(() => (print(11111)));
Future(() => (print(22222)));
Future.microtask(() => (print(33333)));