前语
咱们好,我是未央歌,一个默默无闻的移动开发搬砖者~
众所周知,Java 是一种多线程言语,适量并合适地运用多线程,会极大提高资源利用率和运转效率,但缺陷也明显,比如敞开过多的线程会导致资源和性能的耗费过大
以及多线程共享内存简略死锁
。
而 Dart 则是一种单线程言语,单线程言语就意味着代码履行顺序是有序的,下面结合一个demo带咱们深化了解单线程模型。
demo 示例
点击 APP 右下角的改写按钮,会调用如下办法,读取一个约 2M 大小的 json 文件。
void loadAssetsJson() async {
var jsonStr = await DefaultAssetBundle.of(context).loadString("assets/list.json");
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
}
如下图所示,点击改写按钮之后,中间的 loading 会卡一下。很多同学一看这个代码就知道,必定会卡,解析一个 2M 的文件,而且是同步解析,主页面必定是会卡的。 那假如我换成异步解析呢?还卡不卡?咱们能够脑海中思考下这个问题。
异步解析
void loadAssetsJson() async {
var jsonStr = await DefaultAssetBundle.of(context).loadString("assets/list.json");
// 异步解析
Future(() {
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
}).then((value) {});
}
咱们能够看到,我现已放在异步里解析了,为什么还是会卡呢?咱们能够先思考下这个问题。
前面现已提到了 Dart 是一种单线程言语,单线程言语就意味着代码履行顺序是有序的。当然 Dart 也是支撑异步的。这两点其实并不抵触。
Dart 线程解析
咱们来看看 Dart 的线程,当咱们 main() 办法启动之后,Dart现已敞开了一个线程,这个线程的名字就叫 Isolate。每一个 Isolate 线程都包含了图示的两个行列,一个 Microtask queue
,一个 Event queue
。
如图,Isolate 线程会优先履行 Microtask queue 里的工作,当 Microtask queue 里的工作变成空了,才会去履行 Event queue 里的工作。假如正在履行 Microtask queue 里的工作,那么 Event queue 里的工作就会被阻塞,就会导致渲染、手势响应等都得不到响应(绘制图形,处理鼠标点击,处理文件IO等都是在 Event Queue 里完结)。
所认为了保证功用正常运用不卡顿,尽量少在 Microtask queue 做工作,能够放在 Event queue 做
。
为什么单线程能够做一个异步操作呢?
- 由于 APP 只要在你滑动或许点击操作的时分才会响应工作。没有操作的时分进入等候时刻,两个行列里都是空的。这个时刻正是能够进行异步操作的,所以基于这个特色,单线程模型能够在等候过程中做一些异步操作,由于等候的过程并不是阻塞的,所以给咱们的感觉就像同时在做多件工作,但从头到尾只要一个线程在处理工作。
Future
当办法加上 async 关键字,就代表这个办法敞开了一个异步操作,假如这个办法有回来值,就必须要回来一个 Future。
void loadAssetsJson() async {
var jsonStr = await DefaultAssetBundle.of(context).loadString("assets/list.json");
// 异步解析
Future(() {
...
}).then((value) {});
}
一个 Future 异步任务的履行,相对简略。在咱们声明一个 Future 之后,Dart 会将异步里的代码函数体放在 Event queue 里履行然后回来。这儿注意下,Future 和 then 是放在同一个 Event queue 里的。
假设,我履行 Future 代码之后没有当即履行 then 办法,而是等 Future 履行之后5秒,才调用 then 办法,这时分还是放在同一个 Event queue 里吗?显然是不或许的,咱们看一下源码是怎样实现的。
Future<R> then<R>(FutureOr<R> f(T value), {Function? onError}) {
...
_addListener(new _FutureListener<T, R>.then(result, f, onError));
return result;
}
bool get _mayAddListener => _state <= (_statePendingComplete | _stateIgnoreError);
void _addListener(_FutureListener listener) {
assert(listener._nextListener == null);
if (_mayAddListener) {
// 待完结
listener._nextListener = _resultOrListeners;
_resultOrListeners = listener;
} else {
// 已完结
...
_zone.scheduleMicrotask(() {
_propagateToListeners(this, listener);
});
}
}
能够看到 then 办法里有一个监听,Future 履行之后5秒才调用,很明显是已完结状态,走 else 那里的 scheduleMicrotask() 办法,就是说把 then 里边的办法放到 Microtask queue 里。
Future 为何卡顿
再来说一下刚刚的问题,我现已放在异步里解析了,为什么还是会卡呢?
其实很简略,Future 里的代码或许需求履行10s,也就是 Event queue 需求10s才干履行完。那这个10s内其他代码必定就无法履行了。所以 Future 里的代码履行时刻过长,还是会卡 UI 的。
以 Android 为例,Android的改写频率是60帧/秒,Android体系中每隔16.6ms会发送一次 VSYNC(同步)信号,触发UI的渲染。所以咱们就要考虑下,一旦代码履行时刻超过16.6ms,到底应不应该放在 Future 里履行?
这时分是不是有同学有疑问,我网络恳求也是用 Future 写的,为什么就不卡呢?
这个咱们就需求注意一下,网络恳求不是放在 Dart 层面履行的,它是由操作体系供给的异步线程去履行的,当这个异步履行完体系又回来给 Dart。所以即使 http 恳求需求耗时十几秒,也不会感到卡顿。
compute
既然 Future 履行也会卡顿,那要怎样去优化呢?这时分咱们能够开一个线程操作,Flutter 为咱们封装好了一个 compute()
办法,这个办法能够为咱们开一个线程。咱们用这个办法来优化一下代码,然后再看下履行作用。
void loadAssetsJson() async {
var jsonStr = await DefaultAssetBundle.of(context).loadString("assets/list.json");
var result = compute(parse,jsonStr);
}
static VideoListModel parse(String jsonStr){
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
VideoListModel.fromJson(json.decode(jsonStr));
return VideoListModel.fromJson(json.decode(jsonStr));
}
能够看到此刻点击改写按钮,现已不再卡顿了。遇到一些耗时的操作,这确实是一种比较好的处理方式。
咱们再看看 DefaultAssetBundle.of(context).loadString(“assets/list.json”) 办法里边是怎样履行的。
Future<String> loadString(String key, { bool cache = true }) async {
final ByteData data = await load(key);
if (data == null)
throw FlutterError('Unable to load asset: $key');
// 50 KB of data should take 2-3 ms to parse on a Moto G4, and about 400 s
// on a Pixel 4.
if (data.lengthInBytes < 50 * 1024) {
return utf8.decode(data.buffer.asUint8List());
}
// For strings larger than 50 KB, run the computation in an isolate to
// avoid causing main thread jank.
return compute(_utf8decode, data, debugLabel: 'UTF8 decode for "$key"');
}
从官方源码能够看到,当文件的大小超过 50kb 时,也是选用 compute() 办法开一个线程去操作的。
多线程机制
Dart 作为一个单线程言语,虽然供给了多线程的机制,但是在多线程的资源是阻隔的,两个线程之间资源是不互通的
。
Dart 的多线程数据交互需求从 A 线程传给 B 线程,再由 B 线程回来给 A 线程。而像 Android 在主线程开一个子线程,子线程能够直接拿主线程的数据,而不用让主线程传给子线程。
总结
- Future 适合耗时小于 16ms 的操作
- 能够通过 compute() 进行耗时操作
- Dart 是单线程原因,但也支撑多线程,但是线程间数据不互通
最后
假如你对 Flutter 感兴趣,能够看看我的专栏:
- Flutter
- Flutter问题收集及处理
感谢咱们的支撑,码字实在不易,其间如若有过错,望指出,记得点赞关注加保藏哦 ~