Flutter 异步编程指南作

news2024/12/28 19:53:31

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/796122.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows 11的最新人工智能应用Windows Copilot面世!

Windows Copilot是Windows 11预览版中的一项AI辅助功能。 Windows 11还包括设置应用程序的更改&#xff0c;更广泛的支持压缩文件格式。 上个月&#xff0c;微软宣布将继续其将ChatGPT应用于所有产品的冒险之旅&#xff0c;推出了名为Copilot的新Windows 11功能。几个月前&…

难评的Worldcoin,正在登月中

7月24日&#xff0c;前段时间搅混加密舆论界一汪春水的Worldcoin正式上线代币WLD。 由Openai创始人Sam Altman牵头的Worldcoin项目在其试点之初就备受关注&#xff0c;而该种关注在其融资近一亿美金后更是空前&#xff0c;币种上架的呼声不断。 但当代币真的狼来了时&#xff0…

使用CRM分析数据有哪些功能?

CRM数据分析软件可以帮助企业增强竞争力&#xff0c;并更好地了解客户需求及市场变化&#xff0c;助力企业数据分析&#xff0c;并提供实时更新的数据和分析结果&#xff0c;CRM数据分析软件的主要特点是什么&#xff1f;包括以下6个特点。 CRM数据分析软件的主要功能通常包括…

四、约束-2.演示

【案例】 根据需求&#xff0c;完成表结构的创建 创建一个user表&#xff1a; create table user(id int primary key auto_increment comment 主键,name varchar(10) not null unique comment 姓名,age int check ( age > 0 && age < 120 ) comment 年龄,statu…

《嵌入式系统工程师》精讲视频-希赛网--视频笔记

只看我不熟的内容 P8 04流水线 理论公式必须熟悉 P29 01OSI&#xff0f;RM 1年大概考4分&#xff0c;主要考察前3个知识点 应用层--与用户打交道&#xff1b;表示层--压缩、加密等&#xff1b;会话层--建立、终止会话&#xff1b; 传输层--建立端到端连接&#xff1b;网络层--路…

vue-cli-service requires Node ^12.0.0 || >= 14.0.0

运行npm run serve 时&#xff0c;报错如下&#xff1a; 系统&#xff1a;win7 node版本&#xff1a;13.14.0 解决方法&#xff1a; 根据路径&#xff08;node_modules/vue/cli-service/package.json&#xff09;找到package.json文件&#xff0c;并将engines的node属性值改为…

关于Ubuntu 18.04 LTS环境下运行程序出现的问题

关于Ubuntu 18.04 LTS环境下运行程序出现的问题 1.运行程序时出现以下情况 2.检查版本 strings /lib/x86_64-linux-gnu/libc.so.6 |grep GLIBC_​ 发现Ubuntu18.04下的glibc版本最高为2.27,而现程序所使用的是glibc2.34,所以没办法运行, 3.解决办法 安装glibc2.34库, …

C语言每日一题:7.寻找数组中心下标。

思路一&#xff1a; 暴力求解&#xff1a; 1.定义一个ps作为中间下标去记录下标值。 2.循环下标ps从头到位&#xff0c;定义四个变量分别是left sum_left,right,sum_right… 3.初始化leftps-1和rightps1.当ps0—>就让sum_left0,和psn-1->>>sum_right0; 4.循环结尾判…

深入探索文心千帆大模型平台:实现企业级大模型训练和推理

摘要&#xff1a;本文将介绍百度智能云推出的文心千帆大模型平台&#xff0c;以满足企业和个人客户的需求。通过该平台&#xff0c;用户可以进行大模型训练和推理&#xff0c;并且享受一站式的工具链和环境。作者将分享自己在平台上的亲身体验&#xff0c;并提供相关的代码示例…

阿里云RDS数据库高可用版升级为集群版

文章目录 1.在集群中添加两个只读实例1.1.点击添加只读实例1.2.配置实例的计费方式及所在可用区1.3.设置实例的资源规格1.4.选择实例使用的专有网络1.5.确认参数配置及订单信息1.6.查看购买的只读实例 2.开启RDS数据库的读写分离2.1.查看只读实例提供的集群地址2.2.开启RDS的读…

算法(2)

二叉树 镜像二叉树 树轴对称 第一个节点的左子树与第二个节点的右子树同步递归对比&#xff0c;第一个节点的右子树与第二个节点的左子树同步递归比较。 二叉树序列化、反序列化 当然你也可以根据满二叉树结点位置的标号规律来序列化&#xff0c;还可以根据先序遍历和中序遍…

客户体验:妙鸭相机(AI)与线下摄影的体验比较

Guofu 第 103⭐️ 篇原创文章分享 &#xff08;点击&#x1f446;&#x1f3fb;上方卡片关注我&#xff0c;加⭐️星标⭐️~&#xff09; &#x1f68f; 写在前面 最近 AI 的发展&#xff0c;每天都在刷新着我们的认知&#xff0c;人工智能已经渗透到我们生活的各个角落。其中&…

ssh2-sftp-client实现前端项目自动部署

首先要npm安装插件 npm i ssh2-sftp-client 项目中新建一个js文件 npm run build 之后在终端中 执行这个js文件就可以直接将文件上传到 服务器 import Client from ssh2-sftp-client; import { join } from path;const sftp new Client();const deploy async () > {try…

内存管理:判断对象是否存活

引用计数算法 引用计数算法&#xff08;Reference Counting&#xff09;判断对象是否存活的基本思路是&#xff1a;在对象中添加一个引用计数器&#xff0c;每当有一个地方引用该对象时&#xff0c;计数器的值就加一&#xff1b;当引用失效时&#xff0c;计数器的值就减一&…

Vector - CAPL - 诊断模块函数(TP层配置)

诊断TP层常见参数值 网络层定时参数的放置 常见的时间参数在诊断数据交互中的位置&#xff0c;后续会专门出一期关于ISO 15765-2的介绍。 CAPL对于此类函数介绍 CanTpGetTimeoutAr & CanTpGetTimeoutAr long CanTpGetTimeoutAr(long connHandle); long CanTpSetTimeoutAr(…

RabbitMQ 教程 | RabbitMQ 简介

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

充分了解java阻塞队列机制

多线程基础 1.阻塞队列1.1 什么是 阻塞队列1.2 阻塞队列的特点 1.3 阻塞队列常用方法1.3.1 抛出异常:add、remove、element1.3.2 返回结果但是不抛出异常offer、poll、peek1.3.3 阻塞put和take1.3.4 小结 1.4 常见的阻塞队列1.4.1 ArrayListBlockingQueue1.4.2 LinkedBlockingQ…

国标GB28181协议视频平台EasyCVR修改录像计划等待时间较长的原因排查与解决

音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索、智能告警、服务器集群、语音对讲、云台控制、电子地图、H.265自动转码H.264、平台级联等。为了便于用户二次开发、调用与集成&…

【PMP】有没有项目经理能看得懂这九张图?求挑战

这九张图&#xff0c;全是圈圈我的肺腑之言啊&#xff01;谁痛谁知道&#xff01; 做技术时&#xff0c;就想着30岁就转管理&#xff0c;管理岗位赚得多&#xff0c;结果发现全是烟雾弹。 做技术和代码打交道&#xff0c;做管理跟人打交道。天天开不完的会、说不完的话&#xf…

https证书怎么选择?

https证书即SSL数字证书&#xff0c;是遵守 SSL 协议的一种数字证书&#xff0c;由全球信任的证书颁发机构 (CA) 验证服务器身份后颁发&#xff0c;将 SSL 证书安装在网站服务器上&#xff0c;会激活挂锁和 https 协议。SSL 证书解决了网民登录网站的信任问题&#xff0c;网民可…