likes
comments
collection
share

Flutter —— 异步编程

作者站长头像
站长
· 阅读数 5

1. 异步编程

1.1 Future

Future 类,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则类型为 Future。也就是说首先Future是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。 在网络请求中经常使用异步编程,那么来探索一下Flutter 中的异步编程。

dart是单线程的,所以底层没有锁之类的东西,但是这不代表着dart不能异步,异步不代表多线程。 下面代码运行后发现做其他事情被堵塞住了,这里async不起作用是因为需要搭配Future使用。

Flutter —— 异步编程

将耗时操作使用Future包装起来,这里可以看到做其他事情就不会被堵塞住了,那么现在即使方法不加async也是异步的,因为Future里面已经是异步的了。或者说async不会异步执行,Future才会异步执行。

Flutter —— 异步编程

如果把 print('结束data = $_data'); 放在Future外面那么其就会先于Future里面的代码执行。

Flutter —— 异步编程

1.2 async ,await

那么如何让等待Future里面的执行完在执行后面代码呢?这时候需要用到await。加了await之后,后面的代码就会等待Future里面的执行完之后在执行。这里的await需要搭配async使用,而没有await的情况下,async是没有意义的。await后面的操作也需要是异步的。那么也就是说,Future是用来异步执行的,而async和await搭配是用来让Future里面的某块代码同步执行的。

Flutter —— 异步编程

1.3 Future.then()

Future的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。这里可以使用then来将特定的需要等待的任务放进去,然后不堵塞后面的任务的执行。 如果then没有返回值的话,那么value就是null。

Flutter —— 异步编程

这里看到Future里面return的话那么value就是return的那个值。这个时候,Future里面返回的数据会被Future包装,然后给到了then里面 的value。

Flutter —— 异步编程

1.4 Future.catchError

异步中的错误是用catchError来进行处理的。当异步任务中出现异常之后, 如果调用下列方法就会报错,这是因为任务里面抛出了异常没有处理。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future =  Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  });
  future.then((value) {
    print('value = $value');
  });

  print('做一点其他事情');
}

在.then 下面添加代码

  future.catchError((e){print("捕获到了错误:$e");}); 

运行后发现还是报错,但是捕获到了异常。正常来说如果处理了异常,就不应该报错了,那么这里是执行顺序的问题吗?

Flutter —— 异步编程

将catch和then交换位置后运行,发现还是报错

Flutter —— 异步编程

把then注释掉后发现不报错了。

Flutter —— 异步编程

那么这里要怎么处理呢?这里一般用链式调用。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  }).then((value) {
    print('value = $value');
  }).catchError((e) {
    print("捕获到了错误:$e");
  });

  print('做一点其他事情');
}

或者在.then中添加onError对错误进行处理 Flutter —— 异步编程

 future.then((value) {
    print('value = $value');
  },onError: (error){print("捕获到了错误:$error");});

onError是在.then这一次对错误进行处理,而catchError则是多次链式调用中的错误处理 如果在.then之前catchError了,那么.then中的value就是catchError中传过来的值了。在catchError之前的.then不会执行。 Flutter —— 异步编程 在CatchError之后添加了whenComplete运行后发现还是报出了异常。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  });

      future.catchError((e) {
    print('error = $e');
    return "错误";
  }).then((value) => print('$value'));

  future.whenComplete(() => print("完成了"));

  print('做一点其他事情');
}

这个时候可以在whenComplete之后添加一个catchError,或者使用链式调用。

    future.catchError((e) {
    print('error = $e');
    return "错误";
  }).then((value) => print('$value')).whenComplete(() => print("完成了"));

上面的例子可以看到链式调用能避免大部分的错误,所以在一般都是使用链式调用来进行处理的,并且把catchError放到最后调用,这样出现异常的时候,前面的.then就不会执行了。如果链式太长的话,可以创建方法来让链式更加简洁。

getData() async {
  print('开始data = $_data');
  //耗时操作
  Future future = Future(() {
    for (int i = 0; i < 10000000; i++) {}
    throw Exception('网络异常');
    return "假的网络数据";
  }).then(thenFunc).whenComplete(completeFunc).catchError((e) {
    print('error = $e');
    return "错误";
  });

  print('做一点其他事情');
}

FutureOr thenFunc(String value) {
}

FutureOr<void> completeFunc() {
}

1.5 多个Future

Future是放在队列中的,所以Future的执行是有顺序的。下面的代码结果输出是什么呢?

void main() {
  testFuture();
  print("A");
}

void testFuture() {
   Future((){
    sleep(Duration(seconds: 2));
    print("C");
  });

  print("B");
}

运行后看到是B->A->C; Flutter —— 异步编程 那么下面的代码运行后会打印什么呢?

void main() {
  testFuture();
  print("A");
}

void testFuture() async {
  await Future((){
    sleep(Duration(seconds: 2));
    print("C");
  }).then((value) =>   print("D"));

  print("B");
}

运行后发现是A->C->D->B,这里因为B被C堵塞了,所以A会先执行,然后C执行完之后执行D,最后执行B。 Flutter —— 异步编程 那么下面代码的输出结果是什么呢?

void main() {
  testFuture();
  print("A");
}

void testFuture() async {
   Future((){
    return "任务1";
  }).then((value) =>   print("$value结束"));

   Future((){
     return "任务2";
   }).then((value) =>   print("$value结束"));

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

   Future((){
     return "任务4";
   }).then((value) =>   print("$value结束"));

  print("任务添加完毕");
}

运行后发现是任务添加完毕->A->任务1结束->任务2结束->任务3结束->任务4结束。 Flutter —— 异步编程

那么这里任务一定是按顺序执行的吗?在任务二添加sleep后重新执行,发现任务顺序还是一样的。 Flutter —— 异步编程 这说明这里会按异步任务的添加顺序执行的。

1.6 Future.wait

当需要等待多个Future完成,并收集它们的结果,可以使用Future.wait。这个时候.then就会等wait里面所有任务完成后在执行,然后返回的值可以用数组来取。wait里面的任务同时处理,但是是按添加顺序执行的,而如果是链式执行的话,则是一个执行完在执行下一个。

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

1.7 microtask

Dart的事件循环(event loop)

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

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

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

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

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

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

声明一个 Future 时,Dart 会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行。 当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。 上面说过了microtask队列的优先级比较高,那么使用microtask就可以让任务优先执行。 下面代码的先执行代码1,代码2,A以及B。

void testFuture3() {
  print('外部代码1');
  Future(()=>print('A')).then((value) => print('A结束'));
  Future(()=>print('B')).then((value) => print('B结束'));
  print('外部代码2');
}

添加了微代码后,那么微任务就会在异步任务之前执行。

void testFuture3() {
  print('外部代码1');

  Future(()=>print('A')).then((value) => print('A结束'));
  Future(()=>print('B')).then((value) => print('B结束'));
  scheduleMicrotask(() {
    print("微任务A");
  });
  print('外部代码2');
}

下面的任务执行后打印情况是什么样的呢?这里5一定是先执行的,然后执行微任务3,然后异步任务按照添加的顺序执行,那么就会先执行future1打印1和4,最后执行future2 打印2,所以打印 5 —— 3 —— 1 —— 4 —— 2;

void testFuture4() {
 Future future1 =  Future((){print('1');});
 Future future2 =  Future((){print('2');});
 scheduleMicrotask(() {
   print("微任务3");
 });
 future1.then((value) => print('4'));
 print('5');
}

运行后验证果真是的。

Flutter —— 异步编程

那么这里的打印顺序是什么呢?这里future3最先被添加到队列,所以依然会比 1, 2 优先执行,所以会先打印6,所以打印 5 —— 3 —— 6 —— 1 —— 4 —— 2;

Flutter —— 异步编程

打印结果:

Flutter —— 异步编程

那么如果是这样的话,打印结果会是什么呢?按照图片里的,执行future3的时候会把自身任务执行完再重新开始循环,那么也就是说,这里5,3之后会先打印6,8 ,然后再打印7,然后再打印142。

void testFuture4() {
  Future future3 = Future(() => null);
  future3.then((value) {
    print('6');
    scheduleMicrotask(() {
      print("7");
    });
  }).then((value) =>  print("8"));

  Future future1 = Future(() {
    print('1');
  });

  future1.then((value) => print('4'));
  Future future2 = Future(() {
    print('2');
  });
  scheduleMicrotask(() {
    print("3");
  });

  print('5');
}

打印结果:

Flutter —— 异步编程

那么把.then拆出来的话是什么结果呢?其实这里相当于把.then里面的任务放到微任务里面去了,所以8依然会优先执行。这也是为什么 future1里面的then的任务会比 future2先执行。

  Future future4 =  future3.then((value) {
    print('6');
    scheduleMicrotask(() {
      print("7");
    });
  });
  
  future4.then((value) => print("8"));

打印结果:

Flutter —— 异步编程

2. 总结

  • Dart中的异步
    • Future对象来完成异步操作。
      • 通过工厂构造方法创建Future对象。
      • 参数为Dart的函数
        • 函数的执行代码将被放入事件队列异步执行。
    • async 和 await 。如果Future内部代码希望同步执行,则使用await修饰。被async修饰的函数为异步执行。
    • Future结果处理
      • Future.then 用来注册一个Future完成时要调用的回调
      • Future.catchError注册一个回调,来捕捉Future的error
        • Future.catchError回调只处理原始Future抛出的错误,不能处理回调函数抛出的错误
        • onError只能处理当前Future的错误
      • Future.whenComplete 在Future完成之后总是会调用,不管是错误导致的完成还是正常执行完毕。
  • Dart的事件循环
    • 在Dart中,实际上有两种队列:
      • 事件队列(event queue),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。
      • 微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由 Dart内部产生。
转载自:https://juejin.cn/post/7032581490223366151
评论
请登录