美文网首页
Dart学习4-异步编程

Dart学习4-异步编程

作者: 壹元伍角叁分 | 来源:发表于2022-03-24 16:09 被阅读0次

在dart中不存在线程的概念,也就没有锁类似的概念。如果有耗时操作,就需要引入Isolate机制

1 Isolate机制

void main() {
  // Isolate有一个静态方法。传入的第一个参数是一个方法,这边使用的匿名方法。第二个参数就是传入方法的参数
  Isolate.spawn((message) {
    // 这里执行的代码都是子isolate中
    sleep(const Duration(seconds: 10));
  }, "我说从isolate传递到子isolate的数据");
}

那Isolate之间是怎么交互的呢?主isolate和子isolate的资源不共享

void main() {
  // 创建一个消息接收器
  var mainReceivePort = ReceivePort();
  // 从消息接收器中读取消息
  mainReceivePort.listen((message) {
    // 这里接收从子isolate发来的消息
    print("主isolate接收器接收到消息:$message");

    // 如果消息类型是SendPort,则说明是发送器。
    if (message is SendPort) {
      message.send("我是从主isolate发送到子isolate的消息");
    }
  });

  mainReceivePort.sendPort.send("我是从主isolate发来的消息");

  // 创建一个子isolate
  Isolate.spawn((SendPort mainSendPort) {
    // 用主isolate发来的发送器发送消息就能够在主isolate中接收到消息
    mainSendPort.send("我是从子isolate发送到主isolate的消息");

    // 如果我们需要从主isolate向子isolate中发送消息,那就需要在子isolate中创建一个自己的接收器
    var childReceivePort = ReceivePort();
    // 同样的,给子isolate中的接收器设置监听
    childReceivePort.listen((message) {
      print("子isolate接收器接收到消息:$message");
    });
    // 将子isolate中的发送器发送给主isolate,那样就完成了双向连接
    var childSendPort = childReceivePort.sendPort;
    mainSendPort.send(childSendPort);
  }, mainReceivePort.sendPort); // 将消息接收器中的发送器发送给子isolate
}

ReceivePort不用时需要关闭,否则程序不会结束。

receivePort.close();

2 任务队列

在dart中是由任务驱动的。
同android中handler类似,在dart运行环境中也是靠事件驱动的,通过event loop不停的从队列中获取消息或者事件来驱动整个应用的运行,isolate发过来的消息就是通过loop处理。
但是不同的是在android中每个线程只有一个looper所对应的messageQueue,
而dart中有两个队列,一个叫做event queue(事件队列),另一个叫做microtask queue(微任务队列)
looper会先从微任务队列中获取任务,直到获取完,才去执行事件队列中的任务。
每当执行完事件队列中的一个任务时,又会去检查微任务队列中是否有了任务,如果有,先去执行微任务。

微任务的执行优先级比事件队列任务高

void main() {
  // 在dart中是由任务驱动的。
  // 同android中handler类似,在dart运行环境中也是靠事件驱动的,通过event loop不停的从队列中获取消息或者事件来驱动整个应用的运行,isolate发过来的消息就是通过loop处理。
  // 但是不同的是在android中每个线程只有一个looper所对应的messageQueue,
  // 而dart中有两个队列,一个叫做event queue(事件队列),另一个叫做microtask queue(微任务队列)
  // looper会先从微任务队列中获取任务,直到获取完,才去执行事件队列中的任务。
  // 每当执行完事件队列中的一个任务时,又会去检查微任务队列中是否有了任务,如果有,先去执行微任务。微任务的执行优先级比事件队列任务高
  print("开始执行main方法--- ${DateTime.now()}");

  var receivePort = ReceivePort();
  receivePort.listen((message) {
    print("执行$message ${DateTime.now()}");
  });

  receivePort.sendPort.send("事件任务1");
  Future.microtask(() {
    // 执行微任务
    print("执行微任务1 ${DateTime.now()}");

    // 这边休眠2s,会阻塞。又佐证了在dart中是单线程的。
    sleep(const Duration(seconds: 2));
  });
  receivePort.sendPort.send("事件任务2");
  Future.microtask(() {
    // 执行微任务
    print("执行微任务2 ${DateTime.now()}");
  });
  receivePort.sendPort.send("事件任务3");

  // 如果在这里休眠3s,那上面的任务队列中的任务也都要在3s后才执行
  // 只有当main任务执行完成之后,才会执行任务队列中的任务
  sleep(const Duration(seconds: 3));

  print("main方法执行结束。。。 ${DateTime.now()}");
}

上面的执行打印结果:

开始执行main方法--- 2022-03-23 16:35:37.269329
main方法执行结束。。。 2022-03-23 16:35:40.287288 ----------------3s后才开始执行任务队列中的任务
执行微任务1 2022-03-23 16:35:40.288894 -------------------------------执行微任务1,2s后才开始继续执行任务
执行微任务2 2022-03-23 16:35:42.291514
执行事件任务1 2022-03-23 16:35:42.294005
执行事件任务2 2022-03-23 16:35:42.294192
执行事件任务3 2022-03-23 16:35:42.294231

3 Future

future意为未来,就是将来要执行的事情。需要main方法执行结束后才能执行,所以不会造成阻塞。

void main() {
  // 延迟处理的消息。至少3s后才能执行。
  Future.delayed(Duration(seconds: 1), () {
    // 读取路径下文件的内容,返回的结果类型Future<String>
    Future<String> readAsString =
        File(r"/Users/phoenix/Downloads/test1").readAsString();

    // 通过then来获取到泛型的值。then也有返回值FutureOr<R>,FutureOr是可以返回void,也可以返回Future<R>
    // 这个是模拟返回一个int值。int值可以继续通过then来获取。
    Future<int> then = readAsString.then((value) {
      print("value=$value");
      return 100;
    });

    // 其实我们可以写成链式调用
    File(r"/Users/phoenix/Downloads/test1").readAsString().then((value) {
      print("链式调用 value=$value");
    });
  });
}

4 Stream

Stream(流)在dart中也经常出现,表示发出的一些列的异步数据。

Future表示稍后获得的一个数据,所有的异步的操作的返回值都用future来表示。但是Future只能表示一次异步获得的数据。而Stream表示多次异步获得的数据。

比如IO处理的时候,每次只会读取一部分的数据和一次性读取整个文件相比,Stream的好处是处理过程中内存占用较小。而File.readString(),是一次性读取整个文件的内容,文件很大的话会导致内存占用过大的问题

import 'dart:io';

void main() {
  // 通过流的方式来读写
  File file = File(r"/Users/phoenix/Downloads/text.epub");
  
  // openRead([int? start, int? end]) 可以传入读取的坐标,也可以不传
  // 返回的结果就是对应流的集合
  Stream<List<int>> openRead = file.openRead();
  
  // 设置读的监听
  var listen = openRead.listen((event) {
    // 一次读取65536。读取大文件时,有可能会读取多次
    print("listen(),会调用多次 ${event.length}");
  });
  
  // onData 可以替换掉listen方法
  listen.onData((data) {
    print("onData(),替换掉listen(),会调用多次");
  });

  // 文件读完毕之后的回调
  listen.onDone(() {
    print("文件都读完了");
  });

  // 也可以控制流暂停和重启
  // listen.pause();
  // listen.resume();
}

通过stream进行读写操作

void main() {
  // 通过流的方式来读写
  File file = File(r"/Users/phoenix/Downloads/text.epub");
  File outFile = File(r"/Users/phoenix/Downloads/text2.epub");

  // openRead([int? start, int? end]) 可以传入读取的坐标,也可以不传
  // 返回的结果就是对应流的集合
  Stream<List<int>> openRead = file.openRead();
  var openWrite = outFile.openWrite();
  
  // 方式一:我们可以直接全部写入
  openWrite.addStream(openRead);
  
  // 方式二:通过设置读的监听,分段多次写入
  var listen = openRead.listen((event) {
    openWrite.add(data);
  });
}

stream不能直接设置多个监听,那怎么设置多个监听呢?

方式1:通过BroadcastStream

void main() {
  var openRead = File(r"/Users/phoenix/Downloads/test1").openRead();

  // 调用多次会报以下异常
  // FileSystemException: An async operation is currently pending, path = '/Users/phoenix/Downloads/test1'
  // openRead.listen((event) {});
  // openRead.listen((event) {});

  // 那怎么设置多个监听呢?通过广播模式
  var asBroadcastStream = openRead.asBroadcastStream();
  asBroadcastStream.listen((event) {
    print("listen1 event=$event");
  });
  asBroadcastStream.listen((event) {
    print("listen2 event=$event");
  });
}

方式1:通过StreamController

void main() {
  var streamController = StreamController.broadcast();
  
  // 在发送消息之前设置监听,可以接收到广播
  streamController.stream.listen((event) {
    print("1....接收 $event");
  });
  
  // 发送消息
  streamController.add("消息1");
  
  // 在发送消息之后才去设置监听,不可以接收到广播
  streamController.stream.listen((event) {
    print("2....接收 $event");
  });
}

5 async-await

使用async和await的代码都是异步的,但是看起来很像同步代码。当我们需要获取A的结果,再执行B时,你需要then()->then(),但是利用async与await能够很好的解决回调问题。

  • async

    被async修饰的方法就变成了异步方法。返回值是Future或者void。可以通过then获取到方法的返回值。

    Future<String> readText() async {
      Future<String> readAsString = File(r"/Users/phoenix/Downloads/test1").readAsString();
      return readAsString;
    }
    

    获取返回值:

    void main() {
      readText().then((value) {
        print("读取文本:$value");
      });
    }
    
  • await

    await可以将原本异步的操作变成同步操作。

    Future<String> readText() async {
      // 如果不写await关键字,则readAsString()是一个异步操作。在第一个没有读取完之后就开始执行第二方法了。
      // Future<String> readAsString = File(r"/Users/phoenix/Downloads/test1").readAsString();
      // var readAsString2 = File(r"/Users/phoenix/Downloads/test1").readAsString();
    
      // await 等待future执行完成后再执行后续代码
      // 使用了await关键字,则这段代码就变成同步,会阻塞后续代码的执行
      var readAsString =
          await File(r"/Users/phoenix/Downloads/test1").readAsString();
      var readAsString2 =
          await File(r"/Users/phoenix/Downloads/test1").readAsString();
      return readAsString + readAsString2;
    }
    
    void main() {
      readText().then((value) {
        print("读取文本:$value");
      });
    }
    

相关文章

  • Dart学习4-异步编程

    在dart中不存在线程的概念,也就没有锁类似的概念。如果有耗时操作,就需要引入Isolate机制 1 Isolat...

  • Dart record

    参考 Dart学习笔记(29):异步编程Dart编程字典子不语归来 的 Dart2基础何小有Dart实例教程 数组...

  • Dart 异步编程相关概念简述--从人生三大事说起

    学习 Dart 的异步编程时,需要对异步编程所涉及的相关知识体系进行梳理,我们可根据以下几个发问来逐个了解异步编程...

  • dart异步

    Dart中的异步 Dart语言的异步编程之Future和async-await是杀手级功能

  • 基于FutureBuilder通用网络请求界面封装

    基于FutureBuilder通用网络请求界面封装 Dart异步编程Future 在Dart中,处理异步有两种方式...

  • @@@@@@@@

    \009-享学课堂安卓架构第一期├01 flutter│ ├Dart异步编程(1)-.mp4│ ├Dart异步...

  • Dart异步编程学习札记

    Dart异步编程学习札记 Dart是一门单线程,所以在开发中我们不需要像其他多线程语言一样需要考虑资源竞争问题,也...

  • Dart异步编程-future

    Dart异步编程包含两部分:Future和Stream该篇文章中介绍Future 异步编程:Futures Dar...

  • dart异步编程

    dart是单线程 一定要记得很清楚dart异步不同于java的线程,java线程是抢占式,但dart相当于开辟...

  • Dart 异步编程

    1、 isolate机制 Dart是基于单线程模型的语言。但是在开发当中我们经常会进行耗时操作比如网络请求,这种耗...

网友评论

      本文标题:Dart学习4-异步编程

      本文链接:https://www.haomeiwen.com/subject/quxljrtx.html