flutter 异步编程与多线程

关于多线程与异步:

很多人容易把多线程和异步搞混,实际上这是两个概念。
多线程是开辟另外一个线程来处理事件,每个线程都有单独的事件队列,互不影响,这个新线程和主线程是并列执行的,只不过共享数据空间。
而异步是不阻塞当前线程,将异步任务和当前线程的任务分开,异步任务后面的任务,不会等待异步任务执行完再执行,而是直接执行,与异步任务的回调没有因果关系,从而不影响当前线程的执行。

对于开发者来说dart是一门单线程语言,我们在开发中,可以使用异步操作,来执行耗时操作(比如数据请求,文件读写等),通过Future类和async和await关键字实现异步编程。

Dart的事件循环(event loop)

在Dart中,实际上有两种队列:

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

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

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


异步任务我们用的最多的还是优先级更低的 event queue。Dart为 event queue 的任务建立提供了一层封装,就是我们在Dart中经常用到的Future。

正常情况下,一个 Future 异步任务的执行是相对简单的:

声明一个 Future 时,Dart 会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行。
当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。

Future async与await

Future:默认的Future是异步运行的,也就是把任务放在Future函数体中,这个函数题会被异步执行
async:异步函数标识,一般与await和Future配合使用,如果一个函数是异步的
await:等待异步结果返回,一般加载Future函数体之前,表明后面的代码要等这个Future函数体内的内容执行完在执行,实现同步执行。
单独给函数添加async关键字, 没有意义,函数是否是异步的,主要看Future

Future的使用

Future<T> 类,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则类型为Future<void>。也就是说首先Future是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。

Future工厂构造函数

什么是工厂构造函数?

工厂构造函数是一种构造函数,与普通构造函数不同,工厂函数不会自动生成实例,而是通过代码来决定返回的实例对象。
在Dart中,工厂构造函数的关键字为factory。我们知道,构造函数包含类名构造函数和命名构造方法,在构造方法前加上factory之后变成了工厂构造函数。也就是说factory可以放在类名函数之前,也可以放在命名函数之前。
下面我们通过Future的工厂构造函数,创建一个最简单的Future。

可以看到,Future的工厂构造函数接收一个Dart函数作为参数。这个函数没有参数,返回值是FutureOr<T>类型。

从打印结果可以看出,Future不需要结果时,返回的类型是 Future<void>

注意,是先执行的类型判断,后打印的Future内的操作。

async和await

默认的Future是异步运行的。如果想要我们的Future同步执行,可以通过asyncawait关键字:

可以看到,我们的Future已经同步执行了。await会等待Future执行结束后,才会继续执行后面的代码。

关键字async和await是Dart语言异步支持的一部分。

异步函数即在函数头中包含关键字async的函数。
async:用来表示函数是异步的,定义的函数会返回一个Future对象。
await:后面跟着一个Future,表示等待该异步任务完成,异步任务完成后才会继续往下执行。await只能出现在异步函数内部。能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。
注意:在Dart中,async/await都只是一个语法糖,编译器或解释器最终都会将其转化为一个Promise(Future)的调用链。
在Dart 2.0之前,async函数会立即返回,而无需在async函数体内执行任何代码
所以,如果我们将代码改成下面的这种形式:


当我们使用了async关键字,意味着testFuture函数已经变成了异步函数。

所以会先执行testFuture函数之后的打印。

在执行完打印后,会开始检查microtask queue中是否有任务,若有则执行,直到microtask queue列队为空。因为microtask queue的优先级是最高的。然后再去执行event queue。一般Future创建的事件会插入event queue顺序执行(使用Future.microtask方法例外)。

Future使用

处理Future的结果

对于Future来说,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future只会对应一个结果,要么成功,要么失败。

请记住,Future的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。

Dart提供了下面三个方法用来处理Future的结果。

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future<T> catchError(Function onError, {bool test(Object error)});
Future<T> whenComplete(FutureOr action());

Future.then()

用来注册一个Future完成时要调用的回调。如果 Future 有多个then,它们也会按照链接的先后顺序同步执行,同时也会共用一个event loop。

void testFuture() async {
    Future.value(1).then((value) {
      return Future.value(value + 2);
    }).then((value) {
      return Future.value(value + 3);
    }).then(print);
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
6

同时,then()会在 Future函数体执行完毕后立刻执行:

void testFuture() async {
     var future = new Future.value('a').then((v1) {
      return new Future.value('$v1 b').then((v2) {
        return new Future.value('$v2 c').then((v3) {
          return new Future.value('$v3 d');
        });
      });
    });
    future.then(print, onError: print);
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
a b c d

那么问题来了,如果Future已经执行完毕了,我们再来获取到这个Future的引用,然后再继续调用then()方法。那么此时,Dart会如何处理这种情况?对于这种情况,Dart会将后续加入的then()方法体放入microtask queue,尽快执行:


640-4.gif

1、因为先调用的testFuture()函数,所以先打印future 13。
2、再执行testFuture()后面的打印。
3、开始异步任务执行。
4、首先执行优先级最高的microtask queue任务scheduleMicrotask,打印future 12。
5、然后按照Future的声明顺序再执行,打印future 1.
6、然后到了 futureFinish,打印future 2。此时futureFinish已经执行完毕。所以Dart会将后续通过futureFinish 调用的 then方法放入microtask queue。由于microtask queue的优先级最高。因此 futureFinish 的 then 会最先执行,打印 future 11。
7、然后继续执行event queue里面的future 3。然后执行 then,打印 future 4。8、同时在then方法里向microtask queue里添加了一个微任务。因为此时正在执行的是event queue,所以要等到下一个事件循环才能执行。因此后续的 then 继续同步执行,打印 future 6。本次事件循环结束,下一个事件循环取出 future 5 这个微任务,打印 future 5。
9、microtask queue任务执行完毕。继续执行event queue里的任务。打印 future 7。然后执行 then。这里需要注意的是:此时的 then 返回的是一个新创建的 Future 。因此这个 then,以及后续的 then 都将被被添加到event queue中了。
10、按照顺序继续执行evnet queue里面的任务,打印 future 10。
11、最后一个事件循环,取出evnet queue里面通过future 7的then方法新加入的 future 8,以及后续的 future 9,打印。
12、整个过程结束。

Future.catchError

注册一个回调,来捕捉Future的error:

void testFuture() async {
  new Future.error('Future 发生错误啦!').catchError(print, test: (error) {
    return error is String;
  });
}

testFuture();

print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
Future 发生错误啦!

then中的回调onError和Future.catchError

Future.catchError回调只处理原始Future抛出的错误,不能处理回调函数抛出的错误,onError只能处理当前Future的错误:

void testFuture() async {
    new Future.error('Future 发生错误啦!').then((_) {
       throw 'new error';
    }).catchError((error) {
      print('error: $error');
      throw 'new error2';
    }).then(print, onError:(error) {
      print("handle new error: $error");
    });
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
error: Future 发生错误啦!
handle new error: new error2

Future.whenComplete

Future.whenComplete 在Future完成之后总是会调用,不管是错误导致的完成还是正常执行完毕。比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。并且返回一个Future对象:

void testFuture() async {
    var random = new Random();
    new Future.delayed(new Duration(seconds: 1), () {
        if (random.nextBool()) {
          return 'Future 正常';
        } else {
          throw 'Future 发生错误啦!';
        }
    }).then(print).catchError(print).whenComplete(() {
        print('Future whenComplete!');
    });
}
testFuture();
  
print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
Future 发生错误啦!
Future whenComplete!
在testFuture()执行之后打印。
Future 正常
Future whenComplete!

Future高级用法

Future.wait

等待多个Future完成,并收集它们的结果。有两种情况:

所有Future都有正常结果返回。则Future的返回结果是所有指定Future的结果的集合:

void testFuture() async {
    var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);

    var future2 =

    new Future.delayed(new Duration(seconds: 2), ()  => 2);

    var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);

    Future.wait({future1,future2,future3}).then(print).catchError(print);

}

testFuture();

print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
[1, 2, 3]

其中一个或者几个Future发生错误,产生了error。则Future的返回结果是第一个发生错误的Future的值:

void testFuture() async {
    var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);

    var future2 =

    new Future.delayed(new Duration(seconds: 2), () {

      throw 'Future 发生错误啦!';

    });

    var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);

    Future.wait({future1,future2,future3}).then(print).catchError(print);

}

testFuture();

print("在testFuture()执行之后打印。");

执行结果:

在testFuture()执行之后打印。
Future 发生错误啦!

多线程

flutter是单线程的,但是留有多线程的操作方法
Dart中一般使用Isolate实现多线程。Isolate有独立的内存空间,不用考虑多线程时资源抢夺问题,即不需要锁。所以Isolate更像多进程操作。但是进程之间的通信也就相对麻烦,其使用port通信。

int a = 10;
Future<void> dartIsolate() async {
  print('开始');
  print("外部 a = ${a}");
  Isolate.spawn(funcA,100);

  //创建Port
  ReceivePort port = ReceivePort();
  //创建Isolate
  Isolate ISO = await Isolate.spawn(funcB,port.sendPort);
  port.listen((message) {
    print("portMessage ${message}");
    a = message;
    //关闭端口
    port.close();
    //销毁ISO
    ISO.kill();
  });
  print('结束');
  print("外部 a = ${a}");
}

void funcA(int p) {
  a = p;
  print('funcA');
  print("内部 a = ${a}");//a = 100 说明两个a不在同一片内存空间,间接说明 Isolate其实是开辟一个新的进程
}

void funcB(SendPort sendPort) {
  print('funcB');
  sendPort.send(1000);
}

结果:

flutter: 开始_name = dingding
flutter: 开始
flutter: 外部 a = 10
flutter: 结束_name = dingding
flutter: funcA
flutter: 内部 a = 100
flutter: funcB
flutter: 结束
flutter: 外部 a = 10
flutter: portMessage 1000

更轻的compute

compute 是对Isolate的封装,更轻量的。线程通信更加方便

//compute 是对Isolate的封装,更轻量的。线程通信更加方便
void computeTest() async {
  print("start");
  //compute能直接拿到回调,不需要做多余的进程端口通信
  int x = await compute(funcC,10);
  print('x = ${x}');
  print("end");
}

int funcC(int p) {
  print('funcC');
  return p + 100;
}

打印:

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