乐趣区

关于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 标记决定是否须要执行用户提供的回调。

退出移动版