前语

咱们好,我是未央歌,一个默默无闻的移动开发搬砖者~

众所周知,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 的文件,而且是同步解析,主页面必定是会卡的。

带你深入理解Flutter及Dart单线程模型
那假如我换成异步解析呢?还卡不卡?咱们能够脑海中思考下这个问题。

异步解析

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) {});
}
带你深入理解Flutter及Dart单线程模型

咱们能够看到,我现已放在异步里解析了,为什么还是会卡呢?咱们能够先思考下这个问题。

前面现已提到了 Dart 是一种单线程言语,单线程言语就意味着代码履行顺序是有序的。当然 Dart 也是支撑异步的。这两点其实并不抵触。

Dart 线程解析

带你深入理解Flutter及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));
}
带你深入理解Flutter及Dart单线程模型

能够看到此刻点击改写按钮,现已不再卡顿了。遇到一些耗时的操作,这确实是一种比较好的处理方式。

咱们再看看 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问题收集及处理

感谢咱们的支撑,码字实在不易,其间如若有过错,望指出,记得点赞关注加保藏哦 ~