Flutter - Dart中的异步编程及多线程

Flutter - Dart中的异步编程及多线程

前言


生命在于不断学习,探索未知的世界!!!


今天我们将从Future出发,一步一步探索Dart中的异步编程及相关的多线程知识。之前看网上一些文章说Dart没有多线程,那到底是不是这样呢?今天我们一探究竟!
本文涉及关键词:

  • Dart的事件循环
  • Future 、async和await等
  • isolate
  • compute

Dart的事件循环

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

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

怎么理解这个呢?我们可以借鉴下面这张图看看Dart的事件循环所谓的优先级是怎样的一个过程。


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

在我们日常开发用的多的还是优先级低的事件队列Dartevent queue的任务建立提供了一层封装,就是我们在Dart中经常用到的Future

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

  1. 声明一个 Future 时,Dart 会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行

  2. 当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。

Future 、async和await

首先我们可以引入一个简单的例子,来看看这个Future的基本使用:

void main() {
  getData();
  print('做其他事情');
}
String _data = '0';
void getData() {
  print('开始data = $_data');
  Future(() {
    //耗时操作
    for (int i = 0; i < 1000000000; i++) {
      _data = '网络数据';
    }
  });
  print('结束data=$_data');
}

下面是运行打印结果:


Future异步执行

可以看出,在当前线程中,Future 所包含的任务是异步执行的,先进行Future之后的任务,再执行Future内的函数,如果我们想要同步执行的话,也就是先执行Future 内的函数再执行之后的事件,可用通过关键字asyncawait:

void main() {
  getData();
  print('做其他事情');
}
String _data = '0';
void getData() async {
  print('开始data = $_data');
  await Future(() {
    //耗时操作
    for (int i = 0; i < 1000000000; i++) {
      _data = '网络数据';
    }
  });
  print('结束data=$_data');
}
Future同步执行

可以看到,此时的Future所包含的任务就是同步执行,await 会等待Future执行结束后,才会继续执行后面的代码。而且在添加asyncawait 后,当前getData函数直接返回了,立刻就执行了main函数内后续任务,所以可见添加了async的函数对当前线程后续操作是没有阻拦效果的,提高程序运行效率。

此时,在添加async函数之后,getData这整个函数已经是个异步函数,getData会返回一个Future对象。

异步函数即在函数头中包含关键字async的函数。

  • async: 用来表示函数是异步的,定义的函数会返回一个Future对象。
  • await:后面跟着一个Future,表示等待该异步任务完成,异步任务完成后才会继续往下执行。await只能出现在异步函数内部(await单独存在是没有意义的,要配合async使用)。能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式。

Future.then()

我们先来看看这个then()的用法:

void main() {
  getData();
  print('做其他事情');
}
String _data = '0';
void getData() {
  print('开始data = $_data');
  Future future = Future(() {
    //耗时操作
    for (int i = 0; i < 1000000000; i++) {
      _data = '网络数据';
    }
  });
  future.then((value) => print('结束data=$_data'));
  
  print('再多干点事情');
}

then使用

因为Future 返回的是一个Future对象,获取这个对象,调用then方法,我们发现then里面的任务是在Future任务块结束后执行的。
不妨再多加两个then方法的调用:

  future.then((value) => print('结束data=$_data'));
  future.then((value) => print('结束1data=$_data'));
  future.then((value) => print('结束2data=$_data'));
多个then方法使用

由此可知,then方法是用来注册一个Future完成时要调用的回调。如果 Future 有多个then,它们也会按照链接的先后顺序同步执行,同时也会共用一个event loop。
查看then方法源码可知then方法最终返回也是一个Future对象:

Future then(FutureOr onValue(T value), {Function? onError});

也就是说then方法可以链式调用:

future.then((value) => print('结束data=$_data'))
      .then((value) => print('结束1data=$_data'))
      .then((value) => print('结束2data=$_data'));

打印结果和上面一样一样的。

综合型例子

下面我们利用一个综合型例子来看事件队列、微任务队列及then方法之间的关系:

void testFuture() {
  Future x1 = Future(() => null);
  Future x2 = x1.then((value) {
    print('6');
    scheduleMicrotask(() => print('7'));
  });
  x2.then((value) => print('8'));

  Future x = Future(() => print('1'));
  x.then((value) {
    print('4');
    Future(() => print('9'));
  }).then((value) => print('10'));

  Future(() => print('2'));

  scheduleMicrotask(() => print('3'));

  print('5');
}

执行结果打印:5 3 6 8 7 1 4 10 2 9

分析:

  1. 首先调用testFuture()函数,所以优先打印 5
  2. 执行优先级较高的微任务事件3
  3. 然后按照Future的声明顺序执行,打印6。此时只是将7这个微任务添加到队列中,暂时还没轮到7执行。
  4. 打印完6后,继续立即执行x2.then方法,所以打印8
  5. 打印完8之后,此时队列里有添加进来的优先级高的7,所以打印7
  6. 然后继续向下执行,打印1,接着4,接着就是10。同样,此处的9只是添加进event queue,并不执行。
  7. 你以为此时会打印刚添加进去的9,不不不!要知道任务2是在一开始打印5之前就添加进了event queue, 所以2应该在9之前,10之后打印。9才是最后被添加进事件队列的,所以最后打印。

结论

  • then() 函数是前面Future函数体执行结束后立即执行的,可以看作是一个微任务,优先级高。
  • 微任务队列(microtask queue)优先级高于事件队列(event queue)。

Future.catchError()

顾名思义,catchError()是用来捕捉Future抛出的错误的。

String _data = '0';
void getData() {
  print('开始data = $_data');
  Future future = Future(() {
    //耗时操作
    for (int i = 0; i < 1000000000; i++) {}

    throw '网络错误';
  });
  future
      .then((value) => print('结束data=$value'))
      .catchError((e) => print('捕获到了:' + e.toString()));
  print('再多干点事情');
}

catchError使用

链式调用catchError()函数,最终捕获到了原始Future 所抛出的错误,而我们可以看到此时then()函数暂未打印,说明一旦原Future跑出错误,then()不会执行。

注意点catchError()尽量放在链式调用最后面。

then中的回调onError 和 Future.catchError

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

future.catchError((e) => print('捕获到了:' + e.toString()))
      .then((value) => print('结束data=$value'),onError: (e) => print('then出错误了'));
catchError放前面使用
future.then((value) => print('结束data=$value'),
          onError: (e) => print('then出错误了'))
      .catchError((e) => print('捕获到了:' + e.toString()));
catchError放后面使用

catchError源码:

Future catchError(Function onError, {bool test(Object error)?});

上面两个例子可以看出:

  1. catchError()捕获原future抛出错误后,then()里的onError就不执行了,因为catchError源码可知,catchError()返回的也是一个Future ,且返回的Future无错误抛出,所以当调用then函数时,里面的onError就无错误可处理,不打印。
  2. 而当原Future先调用then()时,此时onError捕获到了原future抛出的错误,并处理打印。然后此情况下的链式调用then返回的Future 无错误抛出(即本次链式调用再无错误抛出),所以最后的catchError无错误处理,暂不打印。

Future.whenComplete()

Future.whenComplete()Future 完成之后总是会调用,不管是抛出错误还是正常执行结束。

future
      .then((value) => print('结束data=$value'))
      .whenComplete(() => print('完成了'))
      .catchError((e) => print('捕获到了:' + e.toString()));
whenComplete放前面
future
      .then((value) => print('结束data=$value'))
      .catchError((e) => print('捕获到了:' + e.toString()))
      .whenComplete(() => print('完成了'));
whenComplete放后面

所以,在Future链式调用中,只要前面的Future完成,whenComplete就会执行。即使是下面这种情况:

future
      .whenComplete(() => print('完成了'))
      .then((value) => print('结束data=$value'))
      .catchError((e) => print('捕获到了:' + e.toString()));
whenComplete放原Future后面

所以,只要当前的Future函数执行体结束,whenComplete就会执行。

Future.wait()

wait()我们只简单介绍下用法:

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

简单理解就是,wait()中的Future执行体依次先执行,然后最后再通过then去处理所有的Future返回的结果。

Dart中的多线程

首先我们从一个简单Future异步任务入手:

void isolateDemo() {
  print('1');
  Future(func);
  sleep(Duration(seconds: 2));
  print('2');
}
func() => print('3');
Future异步

Future 是个异步任务,所以func的执行一定需要等待2任务结束再执行,因为都在一个主线程里。下面我们引入Isolate()

void isolateDemo() {
  print('1');
  Isolate.spawn(func, 10);
  sleep(Duration(seconds: 2));
  print('2');
}
func(int count) => print('3');

Isolate使用

看到没,打印顺序变成了1 3 2,说明3任务没有被2任务所阻拦,也就是说3 任务不在主队列里面执行,而是在子线程里执行。
我们再把代码复杂点:

void isolateDemo() {
  print('1');
  Isolate.spawn(func, 10);
  Isolate.spawn(func1, 10);
  Isolate.spawn(func, 10);
  Isolate.spawn(func1, 10);
  Isolate.spawn(func, 10);
  Isolate.spawn(func1, 10);
  Isolate.spawn(func, 10);
  Isolate.spawn(func1, 10);
  Isolate.spawn(func, 10);
  Isolate.spawn(func1, 10);
  Isolate.spawn(func, 10);
  Isolate.spawn(func1, 10);
  sleep(Duration(seconds: 2));
  print('2');
}
func(int count) => print('第一个来了');
func1(int count) => print('第二个来了');

Isolate使用2

打印结果可见,func()func1()两个函数执行顺序是随机的,也即是说随机开辟了子线程去执行。
Isolate 可以结合端口(port)使用:

void isolateDemo() async {
  print('1');
  //创建一个port
  ReceivePort port = ReceivePort();
  //创建Isolate
  Isolate iso = await Isolate.spawn(func, port.sendPort);
  port.listen((message) {
    a = message;
    print('接收到了$a');
    port.close();
    iso.kill();
  });
  sleep(Duration(seconds: 2));
  print('a = $a');
}
int a = 10;
func(SendPort send) {
  send.send(100);
}

可实现在子线程中修改变量,但是主线程中的变量还是不变。
注意:端口记得关闭port.close(),子线程需要手动杀死iso.kill()

Dart中的Compute()

Dart中还有另外一个Compute可以调起子线程:

void computeDemo() {
  print('1');
  compute(func2, 10);
  sleep(Duration(seconds: 2));
  print('2');
}
func2(int count) => print('3');

Compute使用

打印结果可见,3任务同样也是在子线程中进行的。

异步结合多线程使用

void computeFuncDemo() {
  Future(() => compute(funcC, 123)).then((value) => print('1结束'));
  Future(() => compute(funcC, 123)).then((value) => print('2结束'));
  Future(() => compute(funcC, 123)).then((value) => print('3结束'));
  Future(() => compute(funcC, 123)).then((value) => print('4结束'));
  Future(() => compute(funcC, 123)).then((value) => print('5结束'));
}

funcC(int message) {}

异步多线程1

此时的异步任务中开启了子线程,=> 符号的意思是返回,所以then()的调用也是在子线程中进行。
我们再改一下代码:

void computeFuncDemo() {
  Future(() {
    compute(funcC, 123);
  }).then((value) => print('1结束'));
  Future(() {
    compute(funcC, 123);
  }).then((value) => print('2结束'));
  Future(() {
    compute(funcC, 123);
  }).then((value) => print('3结束'));
  Future(() {
    compute(funcC, 123);
  }).then((value) => print('4结束'));
  Future(() {
    compute(funcC, 123);
  }).then((value) => print('5结束'));
}

funcC(int message) {}

异步多线程2

改过的代码居然顺序执行,这不是偶然,因为此时的then的调用者是Future本身,此处没有=>符号里的返回的意思,所以此时按声明顺序打印。
当在所有compute 之前加上return,结果又不一样了:
异步多线程3

你可能感兴趣的:(Flutter - Dart中的异步编程及多线程)