关于flutter:Flutter-异步编程指南

作者:京东物流 王志明

1 Dart 中的事件循环模型

在 App 开发中,常常会遇到解决异步工作的场景,如网络申请、读写文件等。Android、iOS 应用的是多线程,而在 Flutter 中为单线程事件循环,如下图所示

Dart 中有两个工作队列,别离为 microtask 队列和 event 队列,队列中的工作依照先进先出的程序执行,而 microtask 队列的执行优先级高于 event 队列。在 main 办法执行结束后,会启动事件循环,首先将 microtask 队列中的工作一一执行结束,再去执行 event 队列中的工作,每一个 event 队列中的工作在执行实现后,会再去优先执行 microtask 队列中的工作,如此重复,直到清空所有队列,这个过程就是 Dart 事件循环的解决机制。这种机制能够让咱们更简略的解决异步工作,不必放心锁的问题。咱们能够很容易的预测工作执行的程序,但无奈精确的预测到事件循环何时会解决到你冀望执行的工作。例如创立了一个延时工作,但排在后面的工作完结前是不会解决这个延时工作的,也就说这个工作的等待时间可能会大于指定的延迟时间。

Dart 中的办法一旦开始执行就不会被打断,而 event 队列中的事件还来自于用户输出、IO、定时器、绘制等,这意味着在两个队列中都不适宜执行计算量过大的工作,能力保障晦涩的 UI 绘制和用户事件的疾速响应。而且当一个工作的代码产生异样时,只会打断当前任务,后续工作不受影响,程序更不会退出。从上图还能够看出,将一个工作退出 microtask 队列,能够进步工作优先级,然而个别不倡议这么做,除非比拟紧急的工作并且计算量不大,因为 UI 绘制和解决用户事件是在 event 事件队列中的,滥用 microtask 队列可能会影响用户体验。

总结下 Dart 事件循环的次要概念:

  1. Dart 中有两个队列来执行工作:microtask 队列和 event 队列。
  2. 事件循环在 main 办法执行结束后启动, microtask 队列中的工作会被优先解决。
  3. microtask 队列只解决来自 Dart 外部的工作,event 队列中有来自 Dart 外部的 Future、Timer、isolate message,还有来自零碎的用户输出、IO、UI 绘制等内部事件工作。
  4. Dart 中的办法执行不会被打断,因而两个队列中都不适宜用来执行计算量大的工作。
  5. 一个工作中未被解决的异样只会打断当前任务,后续工作不受影响,程序更不会退出。

1.1 向 microtask 队列中增加工作

能够应用顶层办法 scheduleMicrotask 或者 Future.microtask 办法,如下所示:

scheduleMicrotask(() => print('microtask1'));
Future.microtask(() => print('microtask2'));

应用 Future.microtask 的劣势在于能够在 then 回调中解决工作返回的后果。

1.2 向 event 队列中增加工作

Future(() => print('event task'));

基于以上实践,通过如下代码能够验证 Dart 的事件循环机制:

void main() {
  print('main start');

  Future(() => print('event task1'));

  Future.microtask(() => print('microtask1'));

  Future(() => print('event task1'));

  Future.microtask(() => print('microtask2'));

  print('main stop');

执行后果:

main start
main stop
microtask1
microtask2
event task1
event task1

通过输入后果能够看到,工作的执行程序并不是依照编写代码的程序来的,将工作增加到队列不会立即执行,而执行程序也完全符合后面讲的规定,以后 main 办法中的代码执行结束后,才会去执行队列中的工作,且 microTask 队列的优先级高于 event 队列。

2 Dart 中的异步实现

在 Dart 中通过 Future 来执行异步工作, Future 是对异步工作状态的封装,对工作后果的代理,通过 then 办法能够注册解决工作后果的回调办法。

创立办法 Future 形式:
Future()
Future.delayed()
Future.microtask()
Future.sync()

2.1 Future()

factory Future(FutureOr<T> computation()) {
  _Future<T> result = new _Future<T>();
  Timer.run(() {
    try {
      result._complete(computation());
    } catch (e, s) {
      _completeWithErrorCallback(result, e, s);
    }
  });
  return result;
}

下面是 Future() 的源码,能够看到外部是通过启动一个没有提早的计时器来增加工作的,实用 try catch 来捕捉工作代码中可能呈现的异样,咱们能够在 catchError 回调中来解决异样。

2.2 Future.delayed()

factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
  if (computation == null && !typeAcceptsNull<T>()) {
    throw ArgumentError.value(null, "computation", "The type parameter is not nullable");
  }
  _Future<T> result = new _Future<T>();
  new Timer(duration, () {
    if (computation == null) {
      result._complete(null as T);
    } else {
      try {
        result._complete(computation());
      } catch (e, s) {
        _completeWithErrorCallback(result, e, s);
      }
    }
  });
  return result;
}

Future.delayed() 与 Future() 的区别是通过一个提早的计时器来增加工作。

2.3 Future.microtask()

factory Future.microtask(FutureOr<T> computation()) {
  _Future<T> result = new _Future<T>();
  scheduleMicrotask(() {
    try {
      result._complete(computation());
    } catch (e, s) {
      _completeWithErrorCallback(result, e, s);
    }
  });
  return result;
}

Future.microtask() 是将工作增加到 microtask 队列,通过这种能够很不便通过 then 办法中的回调来解决工作的后果。

2.4 Future.sync()

factory Future.sync(FutureOr<T> computation()) {
  try {
    var result = computation();
    if (result is Future<T>) {
      return result;
    } else {
      // TODO(40014): Remove cast when type promotion works.
      return new _Future<T>.value(result as dynamic);
    }
  } catch (error, stackTrace) {
    var future = new _Future<T>();
    AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
    if (replacement != null) {
      future._asyncCompleteError(replacement.error, replacement.stackTrace);
    } else {
      future._asyncCompleteError(error, stackTrace);
    }
    return future;
  }
}

Future.sync() 中的工作会被立刻执行,不会增加到任何队列。

在第一个章节中讲到了能够很容易的预测工作的执行程序,上面咱们通过一个例子来验证:

void main() {
  print('main start');

  Future.microtask(() => print('microtask1'));

  Future.delayed(new Duration(seconds:1), () => print('delayed event'));
  Future(() => print('event1'));
  Future(() => print('event2'));

  Future.microtask(() => print('microtask2'));

  print('main stop');
}

执行后果:

main start
main stop
microtask1
microtask2
event1
event2
delayed event

因为代码比较简单,通过代码能够很容易的预测到执行后果,上面将复杂度略微进步。

void main() {
  print('main start');

  Future.microtask(() => print('microtask1'));

  Future.delayed(new Duration(seconds:1), () => print('delayed event'));

  Future(() => print('event1'))
    .then((_) => print('event1 - callback1'))
    .then((_) => print('event1 - callback2'));

  Future(() => print('event2')).then((_) {
    print('event2 - callback1');
    return Future(() => print('event4')).then((_) => print('event4 - callback'));
  }).then((_) {
    print('event2 - callback2');
    Future(() => print('event5')).then((_) => print('event5 - callback'));
  }).then((_) {
    print('event2 - callback3');
    Future.microtask(() => print('microtask3'));
  }).then((_) {
    print('event2 - callback4');
  });

  Future(() => print('event3'));

  Future.sync(() => print('sync task'));

  Future.microtask(() => print('microtask2')).then((_) => print('microtask2 - callbak'));

  print('main stop');
}

执行后果:

main start
sync task
main stop

microtask1
microtask2
microtask2 - callbak

event1
event1 - callback1
event1 - callback2

event2
event2 - callback1
event3

event4
event4 - callback

event2 - callback2
event2 - callback3
event2 - callback4

microtask3
event5
event5 - callback

delayed event

看到后果后你可能会纳闷,为什么 event1、event1 – callback1、event1 – callback2 会间断输入,而 event2 – callback1 输入后为什么是 event3,event5、event5 – callback 为什么会在 microtask3 后输入?

这里咱们补充下 then 办法的一些要害常识,了解了这些,下面的输入后果也就很好了解了:

  1. then 办法中的回调并不是依照它们注册的程序来执行。
  2. Future 中的工作执行结束后会立即执行 then 办法中的回调,并且回调不会被增加到任何队列中。
  3. 如果 Future 中的工作在 then 办法调用之前曾经执行结束了,那么会有一个工作被退出到 microtask 队列中。这个工作执行的就是被传入then 办法中的回调。

2.5 catchError、whenComplete

Future(() {
  throw 'error';
}).then((_) {
  print('success');
}).catchError((error) {
  print(error);
}).whenComplete(() {
  print('completed');
});

输入后果:

error
completed

通过 catchError 办法注册的回调,能够用来解决工作代码产生的异样。不论 Future 中的工作执行胜利与否,whenComplete 办法都会被调用。

2.6 async、await

应用 async、await 能以更简洁的编写异步代码,是 Dart 提供的一个语法糖。应用 async 关键字润饰的办法返回值类型为 Future,在 async 办法内能够应用 await 关键字来润饰异步工作,在办法外部达到同步执行的成果,能够达到简化代码和进步可读性的成果,不过如果想要解决异样,须要实用 try catch 语句来包裹 await 润饰的异步工作。

void main() async {
  print(await getData());
}

Future<int> getData() async {
  final a = await Future.delayed(Duration(seconds: 1), () => 1);
  final b = await Future.delayed(Duration(seconds: 1), () => 1);
  return a + b;
}

3 Isolate介绍

后面讲到耗时工作不适宜放到 microtask 队列或 event 队列中执行,会导致 UI 卡顿。那么在 Flutter 中有没有既能够执行耗时工作又不影响 UI 绘制呢,其实是有的,后面提到 microtask 队列和 event 队列是在 main isolate 中运行的,而 isolate 是在线程中运行的,那咱们开启一个新的 isolate 就能够了,相当于开启一个新的线程,应用多线程的形式来执行工作,Flutter 也为咱们提供了相应的 Api。

3.1 compute

void main() async {
  compute<String, String>(
    getData,
    'Alex',
  ).then((result) {
    print(result);
  });
}

String getData(String name) {
  // 模仿耗时3秒
  sleep(Duration(seconds: 3));
  return 'Hello $name';
}

compute 第一个参数是要执行的工作,第二个参数是要向工作发送的音讯,须要留神的是第一个参数只反对顶层参数。应用 compute() 能够不便的执行耗时工作,然而滥用的话也会事与愿违,因为每次调用,相当于新建一个 isolate。下面的代码执行一个经验了 isolate 的创立以及销毁过程,还有数据的传递会经验两次拷贝,因为 isolate 之间是齐全隔离的,不能共享内存,整个过程除去工作自身的执行工夫,也会十分的耗时,isolate 的创立也比拟耗费内存,创立过多的 isolate 还有 OOM 的危险。这时咱们就须要一个更优的解决方案,缩小频繁创立销毁 isolate 所带来的耗费,最好是能创立一个相似于线程池的货色,只有提前初始化好,前面就能够随时应用,不必放心会产生后面所讲的问题,这时候 LoadBalancer 就派上用场了

3.2 LoadBalancer

// 用来创立 LoadBalancer
Future<LoadBalancer> loadBalancerCreator = LoadBalancer.create(2, IsolateRunner.spawn);

// 全局可用的 loadBalancer
late LoadBalancer loadBalancer;

void main() async {
  // 初始化 LoadBalancer
  loadBalancer = await loadBalancerCreator;

  // 应用 LoadBalancer 执行工作
  final result = await loadBalancer.run<String, String>(getData, 'Alex');
  print(result);
}

String getData(String name) {
  // 模仿耗时3秒
  sleep(Duration(seconds: 3));
  return 'Hello $name';
}

应用 LoadBalancer.create() 办法能够创立出一个 isolate 线程池,可能指定 isolate 的数量,并主动实现了负载平衡。利用启动后在适合的机会将其初始化好,后续就有一个全局可用的 LoadBalancer 了。

4 实用教训

4.1 指定工作的执行程序

在开发中常常会有须要间断执行异步工作的场景,例如上面的例子,前面的一步工作间接须要以来后面工作的后果,所有工作失常执行结束才算胜利。

void main() async {
  print(await getData());
}

Future<int> getData() {
  final completer = Completer<int>();
  int value = 0;

  Future(() {
    return 1;
  }).then((result1) {
    value += result1;
    return Future(() {
      return 2;
    }).then((result2) {
      value += result2;
      return Future(() {
        return 3;
      }).then((result3) {
        value += result3;
        completer.complete(value);
      });
    });
  });

  return completer.future;
}

这种形式呈现了回调天堂,代码十分难以浏览,理论开发中还会有解决异样的代码,会显得更加臃肿,编写难度也大,显然这种形式是不倡议应用的。

4.2 应用 then 的链式调用

void main() async {
  print(await getData());
}

Future<int> getData() {
  int value = 0;
  return Future(() => 1).then((result1) {
    value += result1;
    return Future(() => 2);
  }).then((result2) {
    value += result2;
    return Future(() => 3);
  }).then((result3) {
    value += result3;
    return value;
  });
}

回调天堂的问题解决了,代码可读性进步很多。

4.3 应用 async、await

void main() async {
  print(await getData());
}


Future<int> getData() async {
  int value = 0;

  value += await Future(() => 1);
  value += await Future(() => 2);
  value += await Future(() => 3);

  return value;
}

成果不言而喻,代码更加清晰了。

4.4 勾销工作

在后面讲到了 Dart 办法执行时是不能被中断的,这就意味着一个 Future 工作开始后必然会走到实现的状态,然而很多时候咱们须要又勾销一个异步工作,惟一的方法就是在工作完结后不执行回调代码,就能够实现相似勾销的成果。

4.5 CancelableOperation

在 Flutter 的 async 包中,提供了一个 CancelableOperation 给咱们应用,应用它能够很简略的实现勾销工作的需要。

void main() async {
  // 创立一个能够勾销的工作
  final cancelableOperation = CancelableOperation.fromFuture(
    Future(() async {
      print('start');
      await Future.delayed(Duration(seconds: 3)); // 模仿耗时3秒
      print('end');
    }),
    onCancel: () => print('cancel...'),
  );

  // 注册工作完结后的回调
  cancelableOperation.value.then((val) {
    print('finished');
  });

  // 模仿1秒后勾销工作
  Future.delayed(Duration(seconds: 1)).then((_) => cancelableOperation.cancel());
}

CancelableOperation 是对 Future 的代理, 对 Future 的 then 进行了接管,判断 isCanceled 标记决定是否须要执行用户提供的回调。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理