Flutter 异步编程指南 您所在的位置:网站首页 顺序循环队列的实现 Flutter 异步编程指南

Flutter 异步编程指南

2023-04-11 02:16| 来源: 网络整理| 查看: 265

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 computation()) {

_Future result = new _Future();

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 computation()?]) {

if (computation == null && !typeAcceptsNull()) {

throw ArgumentError.value(null, "computation", "The type parameter is not nullable");

}

_Future result = new _Future();

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 computation()) {

_Future result = new _Future();

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 computation()) {

try {

var result = computation();

if (result is Future) {

return result;

} else {

// TODO(40014): Remove cast when type promotion works.

return new _Future.value(result as dynamic);

}

} catch (error, stackTrace) {

var future = new _Future();

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 方法的一些关键知识,理解了这些,上面的输出结果也就很好理解了:

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 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(

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 loadBalancerCreator = LoadBalancer.create(2, IsolateRunner.spawn);

// 全局可用的 loadBalancer

late LoadBalancer loadBalancer;

void main() async {

// 初始化 LoadBalancer

loadBalancer = await loadBalancerCreator;

// 使用 LoadBalancer 执行任务

final result = await loadBalancer.run(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 getData() {

final completer = Completer();

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 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 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 标记决定是否需要执行用户提供的回调。返回搜狐,查看更多



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有