Flutter开发Dart极速入门 (Dart异步详解)

Dart入门系列:
Flutter开发Dart极速入门 (基本类型)
Flutter开发Dart极速入门 (变量)
Flutter开发Dart极速入门 (函数)
Flutter开发Dart极速入门 (操作符与流程控制语句)
Flutter开发Dart极速入门 (异常)
Flutter开发Dart极速入门 (类和对象)
Flutter开发Dart极速入门 (泛型)
Flutter开发Dart极速入门 (Dart异步详解)
Flutter开发Dart极速入门 (生成器)
Flutter开发Dart极速入门 (库的使用)
Flutter插件化开发注意事项(Packages与插件化开发)
Flutter在Android原生工程中的集成

文章目录

  • 异步
    • async和await、
    • then/ catchError/ whenComplete
    • Event-Looper
    • Event Queue 和 Microtask Queue
    • 任务调度
    • new Future() 详解
    • scheduleMicrotask() 详解
  • 隔离 - Isolate

异步

async和await、

  • await 用于等待异步函数的结果

  • 要使用await,代码必须在一个async函数中

  • 尽管async函数可能执行耗时的操作,但它不会等待这些操作。取而代之的是,该async函数仅执行到第一个await表达式。然后,它返回Future对象,只有await表达式完成后才恢复执行。

    main() {
      getName1();
      getName2();
    }
    
    getStr1() => print('getStr1');
    
    getStr2() => print('getStr2');
    
    Future<void> getName1() async {
    //  getStr1();
      await getStr1();  // 返回future对象,await表达式执行完成后继续执行
      await getStr2();  // await表达式可以使用多次
      print('getName1');
    }
    
    getName2() => print('getName2');
    

异步函数除了返回 Future, 也可以返回 Stream, Stream 代表的是数据序列

除了使用listen 函数来监听 stream 里的值, 还可以通过 await for 来获取 stream 里的值, 如:

Stream<int> timedCounter(Duration interval, [int maxCount]) async* {
  int i = 0;
  while (true) {
    await Future.delayed(interval);
    yield i++;
    if (i == maxCount) break;
  }
}

main() async {
  timedCounter(Duration(seconds: 2), 5).listen(print);
}

Stream的单订阅(single) 和 多订阅(broadcast)

Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

then/ catchError/ whenComplete

如果需要监听“完毕”这个状态,那么用whenComplete

需要监听“成功”这个状态,用then

需要监听“失败”这个状态,用catchError

void runFuture() {
  Future(() => getFutureTask()) // 异步任务函数
      .then((i) => '++i ${++i}') // 异步任务执行完成后的子任务
      .then((s) => print('retult s: $s')) // s为上个任务返回的结构
      .then((_) => print('_ is void'))  // _代表无意义的值(上个语句返回的是void), 习惯性写法
      .then((_) => new Future.error('error')) // 返回一个error
      .then((_) => print('error 之后的执行语句.')) // error之后的语句不会执行, 直接跳转到catchError
//      .catchError((e) => print(e))
      .catchError((e) => print(e), test: (o) {  // test: 自己实现处理异常的方法
        print('onTest: $o');
//        return true;  // 处理完成, 如果不实现test(), 默认也是返回true
        return false;   // 没有处理完成, 继续抛出异常
      })
      .catchError((e) => print('catch err2: $e'))
      .whenComplete(() => 'complete.');
}

Event-Looper

  • 所谓的消息循环的就是不断从消息队列中取出消息并处理他们直到消息队列为空。如下图所示

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u8TVPJe5-1587916791157)(file:…\md_pic\image-20200311173821011.png)]

  • 消息队列中的消息可能来自用户输入,文件I/O消息,定时器等。如上图的消息队列就包含了定时器消息和用户输入消息。

  • Dart的机制的main隔离, 如上图所示, Dart中的Main Isolate只有一个Event Looper

  • Event Lopper中存在两个Event Queue: Event Queue(事件队列)以及Microtask Queue(微任务队列)。

Event Queue 和 Microtask Queue

  • 优先全部执行完Microtask Queue中的Event
  • 直到Microtask Queue为空时,才会执行Event Queue中的Event
  • 当Event Looper正在处理Microtask Queue中的Event时候,Event Queue中的Event就停止了处理了,此时App不能绘制任何图形,不能处理任何鼠标点击,不能处理文件IO等等
  • 绘制图形,处理鼠标点击,处理文件IO等都是在Event Queue里完成的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FqQtuUx9-1587916791162)(file:…\md_pic\queue.png)]

任务调度

  • 使用Future类,可以将任务加入到Event Queue的队尾
  • 使用scheduleMicrotask函数,将任务加入到Microtask Queue队尾

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wEJ4TqKV-1587916791166)(file:/…\md_pic\image-20200311174546203.png)]

new Future() 详解

  • 使用new Future() 将创建一个异步任务到Event队列

  • Future中的then并没有创建新的Event丢到Event Queue中,而只是一个普通的Function Call,在FutureTask执行完后,立即开始执行

  • 如果在then()调用之前Future就已经执行完毕了,那么任务会被加入到microtask队列中,并且该任务会执行then()中注册的回调函数

  • Future.sync构造函数执行了它传入的函数之后,也会立即创建Task丢到microtask Queue中执行

  • 当任务需要延迟执行时,可以使用new Future.delay()来将任务延迟执行

好了, 直接看实例

猜一猜下面代码的打印顺序

testFuture() {
  Future f = new Future(() => print('f1'));
//  Future f1 = new Future(() => null);	// 场景1
  Future f1 = new Future.delayed(Duration(seconds: 1));	// 场景2, f1被延迟1s入队
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);

  f3.then((_) => print('f2'));
  f2.then((_) {
    print('f3');
    new Future(() => print('f4'));
    f1.then((_) => print('f5'));
  });
  f1.then((m) => print('f6'));
  print('f7');
}

场景1 的输出结果

//7 1 6 3 5 2 4

//'f7' - 非异步 最先执行
// ---- 下面的顺序代表 Future 队列的顺序 ---------- fn代表队列名称, 'fn'(加引号的)代表输出值
//  f   --> 'f1': 该Future在Event Queue中排第一个, 最先执行
//  f1  --> 'f6'
//  f2  --> 'f3': 执行完成后: 队列中加入新的new Future, 并将'f5'放入f1的微队列(scheduleMicrotask最先执行), 'f5'立刻被执行
//  f3  --> 'f2'
//  new Future  --> 'f4'

场景2, 延迟1s入队的输出结果

// 7 1 3 2 4 6 5

//'f7' - 非异步 最先执行
// ---- 下面的顺序代表 Future 队列的顺序 ---------- fn代表队列名称, 'fn'(加引号的)代表输出值
//  f   --> 'f1': 该Future在Event Queue中排第一个, 最先执行
//  f2  --> 'f3': 执行完成后: 队列中加入新的new Future, 并将'f5'放入f1队列
//  f3  --> 'f2'
//  new Future  --> 'f4'
//  ... 一秒后 ...
//  f1  --> 'f6' 1s后入队, 之后执行'f5'

scheduleMicrotask() 详解

  • 如果可以,尽量将任务放入event队列中
  • 使用Future的then方法或whenComplete方法来指定任务顺序
  • 为了保持你app的可响应性,尽量不要将大计算量的任务放入这两个队列
  • 大计算量的任务放入额外的isolate中

上实例, 猜顺序,

testScheduleMicrotask() {
  scheduleMicrotask(() => print('s1'));

  new Future.delayed(new Duration(seconds: 1), () => print('s2'));
  var f1 = new Future(() => print('s3'));
  f1.then((_) {
      print('s4');
      scheduleMicrotask(() => print('s5'));
    })
    .then((_) => print('s6'));
  var f2 = new Future(() => print('s10'));
  f2.then((_) => new Future(() => print('s11')))
    .then((_) => print('s12'));
  new Future(() => print('s7'));
  scheduleMicrotask(() => print('s8'));
  print('s9');
}

输出结果如下 9 1 8 3 4 6 5 10 7 11 12 2

如果实在猜不到, 就不用猜了

直接听我哔哔吧

// 先打印 's9'
// ----- micro 队列 ------- task 队列 --------
// 's1' // 先执行main中的微队列, 最先输出's1'
// 's8'	// 然后输出's8', delay的's2'当然还是放在最后
// 接下来要按顺序执行Event队列了
// 此时Event队列的顺序是 f1 f2 new('f7')

//                      f1 -> 's3'	// 开始执行f1队列中的内容
//                      f1 -> 's4'
//                      f1 -> 's6'  // f1执行完毕, 开始检查他的微队列
// f1_micro -> 's5'	// f1的微队列被执行完毕

//                      f2 -> 's10'	// 开始执行f2队列

// f2队列中的new Future创建出来了new_f2, f2中未完成的任务都将被放入new f2
// 此时Event队列的顺序是 f1 f2 new('f7') new_f2
// 继续执行, 检查f2的微队列, 微队列内容为空, 继续执行new('f7')

//                      's7'

//                      new f2 -> 's11'
//                      new f2 -> 's12'

//                      's2'

隔离 - Isolate

Dart中使用isolate来代替thread, Isolate不是thread, 是一种独立运行且不共享内存的worker, 所有Dart代码都在自己的隔离区运行;

每个隔离区都有自己的堆内存, 以确保isolate的状态不能被其他任何isolate访问; 像我们执行任务的时候默认的main方法就是一个默认的isolate,可以看出如果我们想在dart中执行多个并行的任务,可以选择创建多个isolate来完成

不同的隔离区可以通过端口发送信息进行通信。主要关注以下几个类:

  1. Isolate:Dart执行上下文的隔离区。
  2. ReceivePort:与SendPort一起,是隔离区之间唯一的通信方式。
  3. SendPort:将消息发送到其他ReceivePort。

代码实例(参考文章):

import 'dart:async';
import 'dart:isolate';

main(List<String> args) => start();

Isolate isolate;
int i = 0;

void start() async {
  //接收消息的主Isolate的端口
  final receive = ReceivePort();
  // runTimer 要执行的回调的函数,可以用来将消息发送回调用者。
  // receive.sendPort 一个端口(SendPort), 是与Isolate通信的方式。
  isolate = await Isolate.spawn(runTimer, receive.sendPort);

  receive.listen((data) {
    print("<<<-- $data ; receive worker i :$i");
    if (data == 'nitification 15') stop();
  });
}

void runTimer(SendPort port) {
  int counter = 0;
  Timer.periodic(const Duration(seconds: 1), (_) {
    final msg = "nitification ${counter++}";
    print("-->>> $msg ; send worker i :${i++}");
    port.send(msg);
  });
}

// 停止隔离区
// 还可以暂停和恢复Isolate的运行,分别对应Isolate中的pause方法和resume方法。
void stop() {
  print("kill isolate");
  isolate?.kill(priority: Isolate.immediate);
  isolate = null;
}

输出结果:

–>>> nitification 0 ; send worker i :0
<<<-- nitification 0 ; receive worker i :0

–>>> nitification 15 ; send worker i :15
<<<-- nitification 15 ; receive worker i :0
kill isolate

两个Isolate中打印的i的值并不一致,则说明Isolate之间的内存并不是共享的。

你可能感兴趣的:(Flutter)