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原生工程中的集成java

异步

async和await、

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

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

  • 尽管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 表明的是数据序列async

除了使用listen 函数来监听 stream 里的值, 还能够经过 await for 来获取 stream 里的值, 如:svg

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 所处的模式。oop

then/ catchError/ whenComplete

若是须要监听“完毕”这个状态,那么用whenCompletepost

须要监听“成功”这个状态,用thenspa

须要监听“失败”这个状态,用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之间的内存并非共享的。