68Dart单线程模型与异步流调用机制

Dart单线程模型以及异步机制

1.简介

​ 不管是在Android/IOS,还是Web、后台、PHP,GO等等编程语言中,都会有同步异步的概念。简单说,同步就是按照代码顺序执行,由上至下。
但是,同步也会导致出现问题阻塞,比如同步时,我们中间需要处理耗时操作,一旦是同步的,就会导致后续事件无法处理,直观的感受案例:我们在手机
上点击屏幕翻页,但是视频可能半分钟之后才响应,也就是阻塞无响应。
​ 在java/swift/kotlin中,如果异步,是需要开子线程去执行(注意kotlin有协程,可以不需要)异步操作,十个耗时任务,可以开启十个子线程同时执行,但同时也带来了大量资源的耗费以及内存共享问题需要加锁解决,复杂情况可能会导致死锁。基于此观念,java后台开发出现了很多著名的框架,springBoot/SpringCloud/k8s等等。

​ 在Dart语言中,只有单线程,那么它是如何处理同步异步问题了。

2.事件异步模型

​ 事件异步模型,简单说:单线程中存在一个事件循环和一个事件队列,比如Android中Handler和MessageQueue就是如此设计的,但也有所不同。事件循环,不断地从事件队列中取数据,遇到耗时的事件时,不会停下来等待结果,而是跳过耗时事件,继续执行下一个事件。等所有耗时事件完成之后,在进行查看耗时的结果。因此,耗时事件不会阻塞整个事件循环,这让它后面的事件也会有机会得到执行
​ 三者对比比较:

在这里插入图片描述

| 模型 | CPU资源 | 内存占用 | 计算密集型 | 复杂度 | I/0流 |
| ------ | ------- | -------- | ---------- | | |
| 单线程 | 1核 | 低 | 低 | 低 | 卡死 |
| 多线程 | 多核 | 高 |低| 高 | 高 |
| 基于事件 | 1核 | 低 |低| 低 | 高 |

通过对比,可以了解到,基于事件模型,不擅长计算密集型的操作,这种异步模型往往用于网络服务器并发。

3.Dart事件模型

​ Dart 是事件驱动的体系结构,该结构基于具有单个事件循环和两个队列的单线程执行模型。 Dart虽然提供调用堆栈。 但是它使用事件在生产者和消费者之间传输上下文。 事件循环由单个线程支持,因此根本不需要同步和锁定。

​ Dart 的两个队列分别是:

  • MicroTask queue 微任务队列
  • Event queue 事件队列

在这里插入图片描述

Dart事件循环执行如上图所示

  • 1.先查看MicroTask队列是否为空,不是则先执行MicroTask队列
  • 2.一个MicroTask执行完后,检查有没有下一个MicroTask,直到MicroTask队列为空,才去执行Event队列
  • 3.在Evnet 队列取出一个事件处理完后,再次返回第一步,去检查MicroTask队列是否为空

总结:MicroTask中任务,需要尽快执行处理,否则Event队列无法执行,Event队列大部分是鼠标按键操作或者I/O流操作,直观感受就是被卡住。

4.使用

1.加入MicroTask队列
import  'dart:async';

void main() {
  //1.使用scheduleMicrotask
  scheduleMicrotask(myTask);
  //2.使用Future对象添加
  new  Future.microtask(myTask);
}

void  myTask(){
  print("this is my task");
}
2.加入Event队列
import  'dart:async';

void main() {
  new  Future(myTask);
}

void  myTask(){
  print("this is my task");
}
3.延迟队列

注意:延迟队列,是加入到Event队列中

import 'dart:async';

void main() {
  new  Future.delayed(new  Duration(seconds:1),(){
    print('task delayed');
  });
}
4.对比是否符合Dart事件模型
import 'dart:async';

void main() {
  print('开始');

  new Future(() {
    print('这是event队列');
  });
  new Future.microtask(() => {
   print('这是micro队列')
  });
  new  Future.delayed(new  Duration(seconds:1),(){
    print('task delayed');
  });
  print('结束');
}

result:

E:\flutter\bin\cache\dart-sdk\bin\dart.exe --enable-asserts F:\DartDemo2\.idea\day01\DartAsync\03.dart
开始
结束
这是micro队列
这是event队列
task delayed

Process finished with exit code 0

5.Future详解

说明:Future类是对未来结果的一个代理,并不是返回任务值,任务值需要获取。

1.创建Future方式
  • Future() //默认Event队列
  • Future.microtask() //mocro队列
  • Future.sync() //立即执行
  • Future.value()
  • Future.delayed()
  • Future.error()

其中sync是同步方法,任务会被立即执行

2.回调

​ 任务完成后,需要一个回调,拿到任务返回结果,回调会被立即执行,不会再加入队列中。

import 'dart:async';

int myTask() {
  return 2;
}

void main() {
  new Future(() {
    return myTask();
  }).then((res) { //链式调用
    print("async task complete $res");
    return res;
  }).then((res) {//链式调用
    print("async task after$res");
  });
}

3.async和await

简化 Future调用:

遇到await之后,会将任务发到队列中

import 'dart:io';

Future<String> doTask() async {
  sleep(Duration(seconds: 2));
  return "ok";
}

// 定义一个函数用于包装
void test() async {
  var r = await doTask();
  print(r);
}

void main() {
    test();
}

​ 模拟链式

void getData() async {
  var res1 = await getNetworkData("agrument1");
  var res2 = await getNetworkData(res1);
  var res3 = await getNetworkData(res2);
  print(res3);
}

Future getNetworkData(String arg) {
  return Future((){
    sleep(Duration(seconds: 3));
    return "Hello word" +arg;
  });
}

6.Isolate

非常耗时的任务添加到事件队列后,仍然会拖慢整个事件循环的处理,甚至是阻塞。可见基于事件循环的异步模型仍然是有很大缺点的,这时候我们就需要Isolate

简单来说,可以将其理解为线程,但是不同于线程,最佳解释为微线程或者协程概念。二者区别在于:线程资源可以共享,有加锁的概念,而协程是没有的,每个协程Isolate都是独立的,每个Isolate都有自己的事件循环,它们之间只能通过发送消息通信,所以它的资源开销低于线程。

这个dart用法中挺麻烦的,暂时过滤,再Flutter中使用比较容易,因为Flutter给我们封装好了。

常见使用场景:

1.超大文件的下载

2.超大文件的拷贝

3.加密

4.图像处理,比如裁剪

7Stream流

StreamFuture都是Dart中异步编程的核心内容

1.什么是Stream流

Stream是Dart语言中的所谓异步数据序列的东西,简单理解,其实就是一个异步数据队列而已。我们知道队列的特点是先进先出的,Stream也正是如此

在这里插入图片描述

更形象的比喻,Stream就像一个传送带。可以将一侧的物品自动运送到另一侧。如上图,在另一侧,如果没有人去抓取,物品就会掉落消失。

在这里插入图片描述

但如果我们在末尾设置一个监听,当物品到达末端时,就可以触发相应的响应行为。

2.Stream流两种类型
  • 单订阅流:单订阅流的特点是只允许存在一个监听器,即使该监听器被取消后,也不允许再次注册监听器
  • 广播流:允许多个监听器存在,就如同广播一样,凡是监听了广播流,每个监听器都能获取到数据
3.Stream单订阅流
1.periodic :指定的间隔时间内生成一个自然数列

//该方法从整数0开始,在指定的间隔时间内生成一个自然数列,以上设置为每一秒生成一次,callback函数用于对生成的整数进行处理,处理后再放入Stream中。这里并未处理,直接返回了。要注意,这个流是无限的,它没有任何一个约束条件使之停止。在后面会介绍如何给流设置条件。
void main(){
  test();
}
test() async{
  // 使用 periodic 创建流,第一个参数为间隔时间,第二个参数为回调函数
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  // await for循环从流中读取
  await for(var i in stream){
    print(i);
  }
}
// 可以在回调函数中对值进行处理修改值
int callback(int value){
  return value * 2;
}

————————可以用于做定时器

2.fromFuture:从一个Future创建Stream

该方法从一个Future创建Stream,当Future执行完成时,就会放入Stream中,而后从Stream中将任务完成的结果取出。这种用法,很像异步任务队列。

//该方法从一个Future创建Stream,当Future执行完成时,就会放入Stream中,而后从Stream中将任务完成的结果取出。这种用法,很像异步任务队列。
void main(){
  test();
}

test() async{
  print("test start");
  Future<String> fut = Future((){
    return "async task";
  });

  // 从Future创建Stream
  Stream<String> stream = Stream<String>.fromFuture(fut);
  await for(var s in stream){
    print(s);
  }
  print("test end");
}
3.fromFutures:多个Future
import  'dart:io';
//从多个Future创建Stream,即将一系列的异步任务放入Stream中,每个Future按顺序执行,执行完成后放入Stream
void main() {
  test();
}

test() async{
  print("test start");
  Future<String> fut1 = Future((){
    // 模拟耗时5秒
    sleep(Duration(seconds:2));
    return "async task1";
  });
  Future<String> fut2 = Future((){
    sleep(Duration(seconds: 2));
    return "async task2";
  });

  // 将多个Future放入一个列表中,将该列表传入
  Stream<String> stream = Stream<String>.fromFutures([fut1,fut2]);
  await for(var s in stream){
    print(s);
  }
  print("test end");
}
4.fromIterable

该方法从一个集合创建Stream,用法与上面例子大致相同

//该方法从一个集合创建Stream,用法与上面例子大致相同
void main(){
  test();
}
test() async{
  // 使用 periodic 创建流,第一个参数为间隔时间,第二个参数为回调函数
// 从一个列表创建`Stream`
  Stream<int> stream = Stream<int>.fromIterable([1,2,3]);
  // await for循环从流中读取
  await for(var i in stream){
    print(i);
  }
}

// 可以在回调函数中对值进行处理修改值
int callback(int value){
  return value * 2;
}
5.values

这是Dart2.5 新增的方法,用于从单个值创建Stream


//这是Dart2.5 新增的方法,用于从单个值创建Stream
void main(){
  test();
}

test() async{
  Stream<bool> stream = Stream<bool>.value(false);
  // await for循环从流中读取
  await for(var i in stream){
    print(i);
  }
}
4.监听器
1.监听器类型

监听Stream,并从中获取数据也有三种方式

  • await for
  • forEach
  • listen

其中listen:

  • onError:发生Error时触发
  • onDone:完成时触发
  • unsubscribeOnError:遇到第一个Error时是否取消监听,默认为false
//该方法从整数0开始,在指定的间隔时间内生成一个自然数列,以上设置为每一秒生成一次,callback函数用于对生成的整数进行处理,处理后再放入Stream中。这里并未处理,直接返回了。要注意,这个流是无限的,它没有任何一个约束条件使之停止。在后面会介绍如何给流设置条件。
import 'dart:async';
void main() {
  test();
}

test() async {
  // 使用 periodic 创建流,第一个参数为间隔时间,第二个参数为回调函数
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  // await for循环从流中读取
  // await for (var i in stream) {
  //   print(i);
  // }
  // print("object");
  // // 使用forEach,传入一个函数进去获取并处理数据
  // stream.forEach((int x) {
  //   print(x);
  // });
  //  stream.listen循环从流中读取
  // stream.listen((x) {
  //   print(x);
  // }, onError: (e) => print(e),
  //     onDone: () => print("onDone")
  // );

  stream.listen(print);//简写,如同kotlin
}

// 可以在回调函数中对值进行处理修改值
int callback(int value) {
  return value * 2;
}
2.take和takwhile

take:用于限制Stream中的元素数量

takewhile:除了限制Stream个数,还判断bool

// Stream<T> take(int count) 用于限制Stream中的元素数量
void main(){
  test();
}


test() async{
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  // 当放入三个元素后,监听会停止,Stream会关闭
  stream = stream.take(3);

  await for(var i in stream){
    print(i);
  }
}

// 可以在回调函数中对值进行处理修改值
int callback(int value){
  return value * 2;
}
// Stream<T> take(int count) 用于限制Stream中的元素数量
void main(){
  test();
}


test() async{
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  // 当放入三个元素后,监听会停止,Stream会关闭
  stream = stream.takeWhile((x){
    // 对当前元素进行判断,不满足条件则取消监听
    return x <= 3;
  });
  await for(var i in stream){
    print(i);
  }
}

// 可以在回调函数中对值进行处理修改值
int callback(int value){
  return value * 2;
}
3.skip:跳过

注意:该方法只是从Stream中获取元素时跳过,被跳过的元素依然是被执行了的,所耗费的时间依然存在,其实只是跳过了执行完的结果而已。

// 表示从Stream中跳过两个元素
void main(){
  test();
}


test() async{
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  stream = stream.take(5);
  // 表示从Stream中跳过两个元素
  stream = stream.skip(2);

  await for(var i in stream){
    print(i);
  }
}

// 可以在回调函数中对值进行处理修改值
int callback(int value){
  return value * 2;
}
4.toList

Stream中所有数据存储在List中

// Future<List<T>> toList() 表示将Stream中所有数据存储在List中
void main() {
  test();
}

test() async {
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  stream = stream.take(5);
  List<int> data = await stream.toList();
  for (var i in data) {
    print(i);
  }
}

// 可以在回调函数中对值进行处理修改值
int callback(int value) {
  return value * 2;
}
5.length:stream数量
//等待并获取流中所有数据的数量
void main() {
  test();
}

test() async{
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), callback);
  stream = stream.take(5);
  var len = await stream.length;
  print(len);
}

// 可以在回调函数中对值进行处理修改值
int callback(int value) {
  return value * 2;
}

5StreamController控制器
  • onListen 注册监听时回调
  • onPause 当流暂停时回调
  • onResume 当流恢复时回调
  • onCancel 当监听器被取消时回调
  • sync 当值为true时表示同步控制器SynchronousStreamController,默认值为false,表示异步控制器
1.放入事件
import 'dart:async';

//它实际上就是Stream的一个帮助类,可用于整个 Stream 过程的控制。
void main() {
  test();
}

test() async{
  // 创建
  StreamController streamController = StreamController();
  // 放入事件
  streamController.add('element_1');
  streamController.addError("this is error");
  streamController.sink.add('element_2');
  streamController.stream.listen(
      print,
      onError: print,
      onDone: ()=>print("onDone"));
}
2.结合take使用
import 'dart:async';

//还可以在StreamController中传入一个指定的stream
void main() {
  test();
}

test() async{
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (e)=>e);
  stream = stream.take(5);

  StreamController sc = StreamController();
  // 将 Stream 传入
  sc.addStream(stream);
  // 监听
  sc.stream.listen(
      print,
      onDone: ()=>print("onDone"));
}

/**
 * onListen 注册监听时回调
    onPause 当流暂停时回调
    onResume 当流恢复时回调
    onCancel 当监听器被取消时回调
    sync 当值为true时表示同步控制器SynchronousStreamController,默认值为false,表示异步控制器
 */
test2() async{
  // 创建
  StreamController sc = StreamController(
      onListen: ()=>print("onListen"),
      onPause: ()=>print("onPause"),
      onResume: ()=>print("onResume"),
      onCancel: ()=>print("onCancel"),
      sync:false
  );

  StreamSubscription ss = sc.stream.listen(print);

  sc.add('element_1');

  // 暂停
  ss.pause();
  // 恢复
  ss.resume();
  // 取消
  ss.cancel();

  // 关闭流
  sc.close();
}
3.暂停恢复取消
import 'dart:async';

//还可以在StreamController中传入一个指定的stream
void main() {
  test2();
}

/**
 * onListen 注册监听时回调
    onPause 当流暂停时回调
    onResume 当流恢复时回调
    onCancel 当监听器被取消时回调
    sync 当值为true时表示同步控制器SynchronousStreamController,默认值为false,表示异步控制器
 */
test2() async{
  // 创建
  StreamController sc = StreamController(
      onListen: ()=>print("onListen"),
      onPause: ()=>print("onPause"),
      onResume: ()=>print("onResume"),
      onCancel: ()=>print("onCancel"),
      sync:false
  );

  StreamSubscription ss = sc.stream.listen(print);

  sc.add('element_1');

  // 暂停
  ss.pause();
  // 恢复
  ss.resume();
  // 取消
  ss.cancel();

  // 关闭流
  sc.close();
}
4.广播流
import 'dart:async';

/**
 * 前面已经说了单订阅流的特点,而广播流则可以允许多个监听器存在,就如同广播一样,凡是监听了广播流,每个监听器都能获取到数据。
 * 要注意,如果在触发事件时将监听者正添加到广播流,则该监听器将不会接收当前正在触发的事件。如果取消监听,监听者会立即停止接收事件。
 * 有两种方式创建广播流,一种直接从Stream创建,另一种使用StreamController创建
 *
 *
 *
 * asBroadcastStream
 */
void main() {
  test();
}

test() async{
  // 调用 Stream 的 asBroadcastStream 方法创建
  Stream<int> stream = Stream<int>.periodic(Duration(seconds: 1), (e)=>e)
      .asBroadcastStream();
  stream = stream.take(5);
  stream.listen(print);
  stream.listen(print);
}

添加事件

import 'dart:async';

/**
 *
 *
 *
 * broadcast
 */
void main() {
  test();
}

test() async{
  // 创建广播流
  StreamController sc = StreamController.broadcast();

  sc.stream.listen(print);
  sc.stream.listen(print);

  sc.add("event1");
  sc.add("event2");
}
5.StreamTransformer:转换

该类可以使我们在Stream上执行数据转换。然后,这些转换被推回到流中,以便该流注册的所有监听器可以接收

factory StreamTransformer.fromHandlers({
      void handleData(S data, EventSink<T> sink),
      void handleError(Object error, StackTrace stackTrace, EventSink<T> sink),
      void handleDone(EventSink<T> sink)
})
  • handleData:响应从流中发出的任何数据事件。提供的参数是来自发出事件的数据,以及EventSink<T>,表示正在进行此转换的当前流的实例
  • handleError:响应从流中发出的任何错误事件
  • handleDone:当流不再有数据要处理时调用。通常在流的close()方法被调用时回调

import 'dart:async';

//StreamController类似于RxJava中flatMap以及map函数


void main() {
  test();
}


void test() {
  StreamController sc = StreamController<int>();

  // 创建 StreamTransformer对象
  StreamTransformer stf = StreamTransformer<int, double>.fromHandlers(
    handleData: (int data, EventSink sink) {
      // 操作数据后,转换为 double 类型
      sink.add((data * 2).toDouble());
    },
    handleError: (error, stacktrace, sink) {
      sink.addError('wrong: $error');
    },
    handleDone: (sink) {
      sink.close();
    },
  );

  // 调用流的transform方法,传入转换对象
  Stream stream = sc.stream.transform(stf);

  stream.listen(print);

  // 添加数据,这里的类型是int
  sc.add(1);
  sc.add(2);
  sc.add(3);

  // 调用后,触发handleDone回调
  // sc.close();
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值