由于Flutter是单线程的,所以java或原生的coder不要认为flutter的异步是多线程
了解java和js的应该知道,java和js区别跟雷锋和雷峰塔、老婆和老婆饼的区别差不多。flutter使用的dart语言,异步操作中更接近js一些
那dart不是多线程,是如何做到不卡的呢?
所谓卡:
屏幕刷新掉帧,cpu忙不过来了。可能是计算量很大,也可能是程序等待
多线程概念里就是把需要等待或者耗时操作放到新的线程里运行,不影响主线程,也就是ui线程
dart是单线程的,为了程序性能和交互体验,觉得可能要使用多线程,dart里是Isolate。但是实际开发中Isolate用的并不多,一是复杂计算可以用compute轻量级异步线程解决,主要还是单线程语言大多使用的是异步。
比如前端开发中,随处可见的async,await,promise
下面学习一下flutter开发中的异步解决方案
Event Loop
flutter程序就运行在一个root Isolate中,事件处理机制是这样的:
程序运行起来,有个Event Loop的事件循环一直在运行,直到程序退出。它主要工作就是不停的从Microtask Queue和EventQueue。这俩队列就是存放异步任务的,FIFO。EventLoop先从Mico队列取任务执行直到做完,然后取EventQueue里的任务
- Micro
优先级高于Event Queue,所以只Microtask Queue里有任务就会立即被翻拍
存放一些微小任务,不会太耗时,不会太多,需要及时响应的,比如手势,ui事件 等。不然一直占着Event Loop,Event Queue事件得不到执行
通过scheduleMicrotask(() => xxx);
添加一个微任务,但不建议使用,微任务由dart调配。高优先级的东西,弄不好可是影响性能的。都是vip就不算vip,都涨工资就不算涨工资😂
可以尝试一下,添加一个耗时任务,页面肯定卡顿
- Event
理解一下这句话:不会有人永远喜欢你,但永远会有人喜欢你
相对微任务而言的,存放一些普通任务,没那么高度的优先级,一直会被运行但又没话语权的。
程序运行中,Event Queue是不会为空的,除非程序要退出
那我有个问题:Event Queue事件处理中,突然有微任务了,咋办?
谨记flutter任务执行顺序即可,main执行完,循环检查microtask队列,如果有就一直把该队列任务执行完成,如果展示没有了,就去检查执行event queue任务。每执行完一个event queue任务就会走一遍循环。所以,当event queue任务执行中来了microtask任务,会把当前event任务执行完成再去把microtask queue执行完。
跳出来说,这种情况可以忽略,本身root isolate也不建议执行非常耗时的任务。如果有耗时任务,可以开辟新的Isolate去执行
我们的代码,哪些调配到Microtask、哪些到Event
- 直接运行
Future.sync()
Future.value()
_.then()
...
- Microtask
scheduleMicrotask()
Future.microtask()
_completed.then()
...
- Event
Future()
Future.delayed()
...
Isolate
单线程机制只能解决等待的问题,但是计算量大的问题没办法解决,毕竟还在ui线程中,所以dart中可以开多个Isolate供耗时任务使用
Future
未来的意思,使用同js中的promise,当发起一个数据请求的时候,会返回一个Future<T>,算是一个承诺,表示未来某时间我会返回一个数据给你。
- then
当正常拿到future数据后,可以用then的方式打开,
- catchError
当处理异常时候,future返回的是异常数据,可以用catchError捕获
- whenComplete
future数据流完成后回调的
- async
异步语法糖,async标记的函数会被封装为Future<T>
- await
语法糖,等待Future事件,是async的反义词,await 标记的返回值是拆封Future后的值
- async 捕获异常
async函数有异常,调用处用try catch捕获异常
关于FutureBuilder
return Center(
child: FutureBuilder(
future: Future.delayed (const Duration(seconds: 2), () => 456),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.connectionState == ConnectionState.done) {
return Text("${snapshot.data}");
}
throw "should not happen";
},
),
);
- future 未来事件,future状态有变化的时候就会调builder
- builder 组件渲染
- snapshot ConnectionState:none、waiting、active(Stream使用,Future不用)、done
- done 表示future完成,但是还需要区分正常/异常
- snapshot:hasData/hasError
注意:如果FutureBuilder入参的有initialData,当future报异常时候,data会被删除,所以环境里data和error只会有一个
关于Stream
Future返回的是一个值,而Stream是事件流,会返回一连串的值。这里Stream并不是表示字节流,而是事件流,一个个的任务/事件
创建事件流
- 自己创建
可以看一下Stream提供的factory方法。都有例子
- 其他返回
一般的如读取本地文件流
- async方式
Stream<int> getNum() async* {
yield 5;
}
获取结果
相对于Future,是通过then方式获得返回值。
而Stream,可以通过监听、StreamBuilder
- stream.listen
是个function回调,使用时候会自动键入
注意:stream监听是一对一的,不能多处监听
- StreamBuilder
StreamBuilder(
stream: stream,
builder: (context, snapshot) {
return Container();
},
)
每当stream有变化的时候,就会回调builder。snapshot跟FutureBuilder使用的时候一样,上面有解释,表示当前事件流的状态。
Stream多一个active状态,表示数据流是活跃的,随时可能产生新的值和错误
Future因为是一个事件,在done状态里判断结束时值的问题,hasData、hasError。
Stream是事件流,所以针对事件值的状态,是在active状态里,hasData、hasError。
done表示Steam结束,关闭了
- 其他api
stream: stream
.where((event) => event > 1)
.map((event) => event + 1)
.distinct(),
stream相关api是很丰富的
关于StreamController
StreamController是一个stream控制器,能实现更精确的Stream控制,内部含有Stream、Sink,以及一些状态管理和功能
Stream是事件流,传输、处理、监听事件的管道。
Sink是一个接收同步/异步事件的对象,可以理解为一个池子。产生事件的源头,还可以添加错误事件。
注意:controller需要关闭,一般在页面生命周期dispose中关闭:
controller.close关闭的是stream,关闭后不能再添加新的事件了。等于是把sink流向stream的口关闭了
controller.sink.close关闭的是sink,表示告诉stream接收器不会再有新的事件添加。stream会继续把剩余的事件处理完。表示sink池子口关闭了
广播
上面说到Stream是不能多处监听的,我们可以把Stream变为广播:StreamController.broadcast()
关于stream缓存
- stream
由于发送和接收是一对一的,所以,如果监听者有延迟监听,stream会缓存事件,当有监听者的时候就一股脑的全发给监听者了
- 广播stream
广播的Stream内的事件不会缓存,它不会为任何接收点留着事件
当广播的某监听者pause后,该监听者内部会缓存事件,等resume的时候会一股脑全处理了