Flutter实战-异步与多线程

1,454 阅读3分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

Future异步

在上面网络请求的时候用到了Future,那么这个到底是什么?Fluture是主线程当中的异步代码

  1. 下面打印的顺序是什么:
void main() {
  testFuture();
  print('A');
}

void testFuture() async {
  Future(() {
    print('C');
  });
  print('B');
}

image.png

  1. 加入await之后打印的顺序是什么?
void main() {
  testFuture();
  print('A');
}

void testFuture() async {
  await Future(() {
    print('C');
  }).then((value) => print('D'));
  print('B');
}

image.png

经过测试发现使用Future修饰的代码块会异步执行,不会卡住当前的线程。如果希望在这个异步任务执行完成之后再操作,需要在Future前面加上一个await。

  1. 多个Future并行的时候的打印顺序:
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('任务添加完毕');
}

image.png

调整顺序或者是让其中的一个任务加入睡眠等多项测试之后,发现多个Future并行发现这个执行的顺序就是添加的顺序。

  1. 有依赖关系的Future打印顺序:
void testFuture() async {
  Future(() {
    return '任务1';
  }).then((value) {
    print('$value结束');
    return '$value任务2';
  }).then((value) {
    print('$value结束');
    return '$value任务3';
  }).then((value) {
    print('$value结束');
    return '$value任务4';
  });
}

image.png

  1. 多个Future执行完成之后再操作:
void testFuture() async {
  Future.wait([
    Future(() {
      return '任务1';
    }),
    Future(() {
      return '任务2';
    }),
    Future(() {
      return '任务3';
    }),
    Future(() {
      return '任务4';
    }),
  ]).then((value) => print('$value')); // wait里面的执行顺序也是添加的顺序
}

image.png

  1. scheduleMicrotask可以插队到Future任务前面
void testFuture() async {
  Future.wait([
    Future(() {
      return '任务1';
    }),
    Future(() {
      return '任务2';
    }),
    Future(() {
      return '任务3';
    }),
    Future(() {
      return '任务4';
    }),
  ]).then((value) => print('$value'));

  scheduleMicrotask(() {
    print('scheduleMicrotask');
  });
}

image.png

Dart中就只有两种队列:一种是事件队列,一种是微任务队列,微任务队列的优先级始终高于事件队列。 下图为官方图:

image.png

  1. 下列打印的顺序是什么?
void testFuture() async {
  Future x = Future(() => print('A'));
  Future(() => print('B'));
  scheduleMicrotask(() {
    print('C');
  });
  x.then((value) => print('D'));
  print('E');
}

image.png

首先肯定是主线程的E,然后是微任务的C,剩下的两个Future按照添加的顺序执行,首先执行A和D最后是B

  1. Fluture中嵌套微任务的执行顺序:
void testFuture() async {
  Future(() => print('A')).then((value) {
    scheduleMicrotask(() {
      print('D');
    });
  }).then((value) => print('F'));
  Future(() => print('B'));
  scheduleMicrotask(() {
    print('C');
  });
  print('E');
}

image.png

这里可以这么理解:then后面的代码可以理解为丢到了微任务队列去执行。

补充:使用Flutter的时候尽量使用链式调用,保证在最后调用.catchError来捕获异常

getData() async {
  print('开始了');
  await Future(() {
    for (int i = 0; i < 100; i++) {}
    return '循环结束';
  })
      .then((value) => print('$value'))
      .whenComplete(() => print('完成了'))
      .catchError((e) => print(e.toString()));
  print('await之后的代码');
}

Dart中的多线程

Dart当中的Isolate更像一个进程,有独立的内存空间。意味着每个进程之间的数据是独立的,不存在抢夺空间的情况,所以不需要锁的概念。

void main() {
  IsolateDemo();
}

void IsolateDemo() {
  print('1');
  Isolate.spawn(func, 3);
  Isolate.spawn(func, 4);
  Isolate.spawn(func, 5);
  Isolate.spawn(func, 6);
  Isolate.spawn(func, 7);
  Isolate.spawn(func, 8);
  Isolate.spawn(func, 9);
  sleep(Duration(seconds: 3));
  print('2');
}

image.png 上面说了不存在同一块内存的问题,我们也来验证一下,在下面的代码中,我在Isolate中修改了a的值,那么最后打印的a=?

int a = 100;
void IsolateDemo() {
  print('1');
  Isolate.spawn(func, 3);
  Isolate.spawn(func, 4);
  Isolate.spawn(func, 5);
  Isolate.spawn(func, 6);
  Isolate.spawn(func, 7);
  Isolate.spawn(func, 8);
  Isolate.spawn(func, 9);
  sleep(Duration(seconds: 3));
  print('2');
  print('a = $a');
}

func(int count) {
  print('$count');
  a = count;
}

image.png 经验证,最后a还是等于100.也就是说在Isolate中的修改并没有效果。 ​

ReceivePort & Isolate

void IsolateDemo() {
  print('1');
  ReceivePort port = ReceivePort();
  Isolate.spawn(func, port.sendPort);
  port.listen((message) {
    print('message=$message');
  });
  sleep(Duration(seconds: 3));
  print('2');
}

func(SendPort port) {
  port.send(10);
}

image.png

上面的代码还是有一点需要完善的地方:此时注意需要手动的关闭端口和销毁Isolate

void IsolateDemo() async {
  print('1');
  ReceivePort port = ReceivePort();
  Isolate iso = await Isolate.spawn(func, port.sendPort);
  port.listen((message) {
    print('message=$message');
    port.close();
    iso.kill();
  });
  sleep(Duration(seconds: 3));
  print('2');
}

compute

用法跟Isolate差不多,是在Isolate上的进一步包装。compute不需要手动kill

void main() {
  Comouterdemo();
}

void Comouterdemo() {
  print('1');
  compute(func1, 10);
  sleep(Duration(seconds: 3));
  print('2');
}

func1(int num) {
  print('$num');
}

image.png

搭配await异步使用

void main() {
  Comouterdemo();
}

void Comouterdemo() async {
  print('1');
  int result = await compute(func1, 10);
  sleep(Duration(seconds: 3));
  print('result=$result');
  print('2');
}

int func1(int num) {
  print('$num');
  return num;
}

image.png

一些小知识

  • 关于import, as关键字就是给库起别名,目的是防止类名、方法名冲突import 'package:http/http.dart' as http;
  • 导入库,默认是整个文件都会导入,如果需要指定的话有两个关键字。show:执行需要导入的内容;hide:需要隐藏的内容
  • pubspec.yamlpublish_to:指定发布到哪里去,默认的都是到pub.dev里面去
  • pubspec.yamlversion: 当前项目的版本号
  • pubspec.yaml中 Dart的版本environment:sdk: ">=2.12.0 <3.0.0"
  • pubspec.yamldev_dependencies: 开发环境依赖的版本打包的时候不会有这些
  • pubspec.yamldependencies: 第三方库导入位置。dio:^4.0.1大版本不变的区间写法,相当于>=4.0.1 <5.0.0; dio:4.0.1指定4.0.1版本; dio:any任意版本