Flutter 中异步任务及多线程介绍



异常的捕获

onError 与 catchError 的区别

getdata() async {
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
  });

  // future.then((value) => print('value=$value'),
  //     onError: (e)=>print(e.toString()));
  
  future
      .then((value) => print('value=$value'))
      .catchError((e)=>print('使用的时候捕获到了异常:' + e.toString()));
}

当我们在 Future 的闭包中抛出异常的时候可以使用 onError 或者 catchError 进行捕获异常并处理,它们的区别就是 onError 是写在 then 里面的,是针对 then 这个事件。

catchError 的使用顺序

虽然我们上面已经通过 catchError 捕获了异常,但是当我们后面再调用 whenComplete 的时候还需要再次调用 catchError,要不然程序还是会抛出异常。正确代码如下。

future
      .then((value) => print('value=$value'))
      .catchError((e)=>print('使用的时候捕获到了异常:' + e.toString()));

  future
      .whenComplete(() => print('完成了'))
      .catchError((e)=>print('完成的时候捕获到了异常:' + e.toString()));

Future 链式调用

Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
  })
      .then((value) => print('value=$value'))
      .whenComplete(() => print('完成了'))
      .catchError((e)=>print('捕获到了异常:' + e.toString()));

针对以上代码我们我们可以使用链式调用进行简写,但是需要注意一点的是 catchError 最好放到最后面。

代码抽取

getdata() async {
  Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
  })
      .then(thenFunc)
      .whenComplete(() => print('完成了'))
      .catchError((e)=>print('捕获到了异常:' + e.toString()));
}

FutureOr thenFunc(Never value) {
  print('value=$value');
}

then 为例,当我们闭包中代码量比较多的时候,我们可以在外面定义一个方法,对代码进行抽离。

多个异步任务的处理

Future 任务顺序执行

testFuture() async {
  Future(() {
    return '任务 1';
  }).then((value) => print('value=$value'));

  Future(() {
    sleep(Duration(seconds: 1));
    return '任务 2';
  }).then((value) => print('value=$value'));

  Future(() {
    return '任务 3';
  }).then((value) => print('value=$value'));
}

这里因为 Future 任务都是在同一个队列中,且 Flutter 是单线程,所以这里任务的执行顺序是按照 Future 的添加顺序执行的,任务1 -> 任务2 -> 任务3

任务前后依赖

后面的任务依赖前一个任务的结果

Future(() {
    return '任务 1';
  }).then((value) {
    print('$value结束');
    return '任务 2';
  }).then((value) {
    print('$value结束');
    return '任务 3';
  });

当我们后面的任务需要依赖前一个任务的执行结果的时候,可以在 then 的闭包中执行下一个任务,并把结果数据返回出去。

多个任务结束统一处理

Future.wait([
  Future(() {
    return '任务 1';
  }),
  Future(() {
  return '任务 2';
  }),
  Future(() {
  return '任务 3';
  })
  ]).then((value) => print(value[0] + value[1] + value[2]));

当我们碰到这样一个需求,同时请求多个接口,在这些接口都请求完成的时候统一处理,这时候我们就可以使用 Future.waitFuture.wait 里面是一个数组,可以放入多个 Future 任务,在这些任务都执行完毕的时候会调用 then 方法,这时候 value 是一个数组,装的是这几个 Future 任务的返回结果。这里 value 数组的顺序跟 Future 任务的顺序是一样的,且 Future 任务也是顺序执行的。

Dart 事件循环

通过上图案例我们可以看到 scheduleMicrotask 中的任务会比 Future 中的任务先执行,这里是因为在 Dart 中有两种队列,事件队列跟微任务队列。

  • 事件队列(event queue),包含所有的外来事件:I/Omouse eventsdrawing eventstimersisolate 之间的信息传递。
  • 微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于 event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue 添加的任务主要是由 Dart 内部产生。

因为 microtask queue 的优先级高于 event queue ,所以如果 microtask queue 有太多的微任务, 那么就可能会霸占住当前的 event loop。从而对 event queue 中的触摸、绘制等外部事件造成阻塞卡顿。

在每一次事件循环中,Dart 总是先去 microtask queue 中查询是否有可执行的任务,如果没有,才会处理后续的 event queue 中的任务。

相关案例

  • 案例 1
Future f = Future(() => print('1'));
  Future(() => print('2'));
  scheduleMicrotask(() => print('3'));
  f.then((value) => print('4'));
  print('5');

这里打印的顺序是 5、3、1、4、2。

  • 案例 2
Future f = Future(() => print('1'));
  Future(() => print('2'));
  scheduleMicrotask(() => print('3'));
  f.then((value) {
    print('4');
    scheduleMicrotask(() => print('5'));
  }).then((value) => print('6'));
  print('7');

这里打印的顺序是 7、3、1、4、6、5、2,这里 6 相当于 f.then 里面的任务, then 中的任务相当于被添加到了微任务队列,所以 4、6 在 5 之前执行。

Dart 中的多线程 Isolate

  Isolate.spawn(func1, 10);
  Isolate.spawn(func2, 20);
  Isolate.spawn(func3, 30);

如上代码中 func1func2func3 会在子线程中执行,且是无序的。DartIsolate 更像是一个进程,它有独立的内存空间,也就意味着每个进程中的空间是独立的,所以不存在资源抢夺的问题,所以不需要锁,这样的话用起来就非常便捷。但是也有一些需要注意的问题,数据不能直接访问,下面我们来看一下。

这里我们在 func 中对 a 的值进行了修改,但是第二次打印的时候可以看到 a 的值还是 10。因为 func 中的 a 被独立起来了,与外部相互之间不能共享。如果想让 funca 的值的修改能在外部起作用的话就需要用到端口。

int a = 10;

IsolateDemo() async {
  //创建 port
  ReceivePort port = ReceivePort();
  //创建 Isolate
  Isolate iso = await Isolate.spawn(func, port.sendPort);
  //通过 port 监听数据变化
  port.listen((message) {
    a = message;
    print('接收到了:a = $a');
    
    //关闭端口
    port.close();
    iso.kill();
  });
}

func(SendPort send) {
  send.send(100);
  print('第一次打印:a = $a');
}

这里我们可以定义一个 port,在 func 中传入 port.sendPort,这时候在 func 方法中发送数据,在 listen 闭包中就可以监听到。端口使用完毕后要调用 close 关闭端口,调用 kill 销毁 Isolate

computeDemo() async {
  int a = await compute(func1, 10);
}

int func1(int count) {
  return 100;
}

Dart 中使用多线程除了 Isolate,还有 computecompute 是基于 Isolate 的封装,用法与 Isolate 类似,有区别的就是 compute 可以接收函数中的返回值。

扩展

pubspec.yaml 文件介绍

  • name:项目名称,必填字段。
  • description:项目描述,非必填字段。
  • publish_to:代表要发布的平台,none 的话代表不发布。
  • version:工程的版本号。
  • dependencies:可以设置 flutter 的版本,默认是获取最新版本。
  • environment:可以指定 dart 版本的兼容范围。
  • dev_dependencies:代表开发环境下的指定版本,打包的时候不会被打包。
  • flutter:字体以及图片都是在 flutter 下面设置。
  • dio: ^4.0.1: 以 dio 三方库为例,^4.0.1 代表大版本区间不变的写法,相当于 >= 4.0.1, < 5.0.0dio: 4.0.1 代表指定版本,dio: any 代表任意版本,dio:>3.0.1`` 代表版本号大于 3.0.1。

import 介绍

import 'package:http/http.dart' as http;

http 为例,当我们导入 import 的时候这里可以看到 asas 的作用就是给库起别名,防止类名或者方法名冲突。导入库的时候默认是整个文件都导入,关键字 show 代表只要导入的内容,hide 代表不需要导入的内容,我们可以根据需要进行指定。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容