当前位置: 首页 > news >正文

Flutter (三) Dart 语言基础详解 (异步,生成器,隔离,元数据,注释)

异步

简介

对于异步不太了解,可以直接看官网介绍;

Dart 有一些语言特性来支持异步编程。最常见的特性是 async 方法和 await 表达式。

Dart 库中有很多返回 Future 或者 Stream 对象的方法。 这些方法是异步的 : 这些函数在设置完基本的操作后就返回了 , 而无需等待操作执行完成。 例如读取一个文件 , 在打开文件后就返回了。

async 和 await

有两种方式可以使用 Future 对象中的数据:

  • 使用 async 和 await
  • 使用 Future API

使用 asyncawait 的代码是异步的, 但是看起来有点像同步代码。 例如,下面是一些使用 await 来 等待异步方法返回的示例:

await lookUpVersion();
复制代码

要使用 await,其方法必须带有 async 关键字:

checkVersion() saync {
   var version =  await lookUpVersion();
    if(version == expectedVersion){
        // TODO ------
    } else {
         // TODO ------
    }
} 
复制代码

可以使用 try, catch, 和 finally 来处理使用 await 的异常:

void getHttp(){
  try{
    var response = await Dio().get("http://www.baidu.com");
    print(response);
  }catch(e){
    print(e);
  }
}
复制代码

声明异步方法

  • 一个 async 方法 是函数体被标记为 async 的方法。 虽然异步方法的执行可能需要一定时间,但是 异步方法立刻返回 - 在方法体还没执行之前就返回了。

    void getHttp async {
        // TODO ---
    }
    复制代码

在一个方法上添加 async 关键字,则这个方法返回值为 Future。 例如,下面是一个返回字符串的同步方法:

String loadAppVersion() => "1.0.2"
复制代码

如果使用 async 关键字,则该方法返回一个 Future,并且 认为该函数是一个耗时的操作。

Futre<String> loadAppVersion() async  => "1.0.2"
复制代码

注意,方法的函数体并不需要使用 Future API。 Dart 会自动在需要的时候创建 Future 对象。

推荐使用 async / await 而不是直接使用底层的特性

  • 好的习惯
void main() {
 //调用异步方法
 doAsync();
}

// 在函数上声明了 async 表明这是一个异步方法
Future<bool> doAsync() async {
  try {
    // 这里是一个模拟请求一个网络耗时操作
    var result = await getHttp();
    //请求出来的结果
    return printResult(result);
  } catch (e) {
    print(e);
    return false;
  }
}
//将请求出来的结果打印出来
Future<bool> printResult(summary) {
  print(summary);
}

//开始模拟网络请求 等待 5 秒返回一个字符串
getHttp() {
 return new Future.delayed(Duration(seconds: 5), () => "Request Succeeded");
}
复制代码
  • 坏的习惯写法
void main() {
 doAsync();
}

Future<String> doAsync() async {
    return  getHttp().then((r){
      return printResult(r);
    }).catchError((e){
      print(e);
    });
}

Future<String> printResult(summary) {
  print(summary);
}

Future<String> getHttp() {
 return new Future.delayed(Duration(seconds: 5), () => "Request Succeeded");
}
复制代码

then,catchError,whenComplete

先来看一段代码

void doAsyncs() async{
  //then catchError whenComplete
  new Future(() => futureTask()) //  异步任务的函数
      .then((m) => "1-:$m") //   任务执行完后的子任务
      .then((m) => print('2-$m')) //  其中m为上个任务执行完后的返回的结果
      .then((_) => new Future.error('3-:error'))
      .then((m) => print('4-'))
      .whenComplete(() => print('5-')) //不是最后执行whenComplete,通常放到最后回调

//      .catchError((e) => print(e))//如果不写test默认实现一个返回true的test方法
      .catchError((e) => print('6-catchError:' + e), test: (Object o) {
    print('7-:' + o);
    return true; //返回true,会被catchError捕获
//        return false; //返回false,继续抛出错误,会被下一个catchError捕获
  })
  .then((_) => new Future.error('11-:error'))
      .then((m) => print('10-'))
      .catchError((e) => print('8-:' + e))
      ;
}

 futureTask() {
//  throw 'error';
  return Future.delayed(Duration(seconds: 5),()  => "9-走去跑步");
}
复制代码

大家猜一猜,看下是怎么样的一个执行流程;我直接放上答案吧,我们来分析下

2-1-:9-走去跑步
5-
7-:3-:error
6-catchError:3-:error
8-:11-:error
复制代码

当异步函数 futureTask() 执行完会在内存中保存 ‘9-走去跑步’ 然后继续执行下一步 这个时候遇见了 then 现在会在内存中保存 “1-: 9-走去跑步 ” 继续执行 这个时候遇见了打印输出 2-1-:9-走去跑步 。现在第一个打印出来了。接着执行下一个 then() 这个时候遇见了一个 error 异常,Dart 会把这个异常保存在内存直到遇见捕获异常的地方。下面执行 whenComplete 这个函数 打印 5- 。然后遇见了一个捕获异常的函数 catchError 如果 test 返回 true ,会被 catchError 捕获 打印 7-:3-:error 6-catchError:3-:error。如果返回 false 只打印 7-:3-:error,会把 error 抛给下一个 catchError 。继续执行 又遇见了一个 error 11-:error ,现在出现 error 了 所以 then 10- 就不会执行了 。最后就直接捕获异常 打印 "8-11-error"。

分析完了,大家会了吗?相信大家差不多会了!

Event-Looper

以下内容从官网所得到

Dart 是单线程模型 Main, 也就没有了所谓的主线程/子线程之分。

Dart 也是 Event-Loop 以及 Event - Queue 的模型,所有的事件都是通过 Event-loop 的依次执行。

而 Dart 的 Event Loop 就是:

  • 从 EventQueue 中获取 Event

  • 处理 Event

  • 直到 EventQueue 为空

而这些 Event 包括了用户输入,点击, Timer, 文件 IO 等

单线程模型

一旦某个 Dart 的函数开始执行,它将执行到这个函数结束,也就是 Dart 的函数不会被其他 Dart 代码打断。

Dart 中没有线程的概念,只有 isolate(隔离),每个 isolate 都是隔离的,并不会共享内存。而一个 Dart 程序是在 Main isolate 的 main 函数开始,而在 Main 函数结束后,Main isolate 线程开始一个一个(one by one)的开始处理 Event Queue 中的每一个 Event 。

Event Queue 和 Microtask Queue

Dart 中的 Main Isolate 只有一个 Event - Looper,但是存在两个 Event Queue : Event Queue 以及 Microtask Queue Microtask Queue 存在的意义是: 希望通过这个 Queue 来处理稍晚一些的事情,但是在下一个消息到来之前需要处理完的事情。 当 Event Looper 正在处理 Microtask Queue 中的 Event 时候,Event Queue 中的 Event 就停止了处理了,此时 App 不能绘制任何图形,不能处理任何鼠标点击,不能处理文件 IO 等等。

Event-Looper 挑选 Task 的执行顺序为:

  • 优先全部执行完 Microtask Queue.

  • 直到 Microtask Queue 为空时,才会执行 Event Queue 中的 Event.

Dart 中只能知道 Event 处理的先后顺序,但是并不知道某个 Event 执行的具体时间点,因为它的处理模型是一个单线程循环,而不是基于时钟调度(即它的执行只是按照 Event 处理完,就开始循环下一个 Event,而与Java 中的 Thread 调度不一样,没有时间调度的概念),也就是我们既是指定另一个 Delay Time 的 Task,希望它在预期的时间后开始执行,它有可能不会在那个时间执行,需要看是否前面的 Event 是否已经 Dequeue 。

任务调度

当有代码可以在后续任务执行的时候,有两种方式,通过dart:async这个Lib中的API即可:

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

当使用 EventQueue 时,需要考虑清楚,尽量避免 microtask queue 过于庞大,否则会阻塞其他事件的处理

new Future()

先来看一段代码

void testFuture() {
  Future f = new Future(() => print("1"));
  Future f1 = new Future(() => null);
  Future f2 = new Future(() => null);
  Future f3 = new Future(() => null);
    
  f3.then((_) => print("2"));
  f2.then((_) {
    print("3");
    new Future(() => print("4"));
    f1.then((_) {
      print("5");
    });
  });
  f1.then((m) {
    print("6");
  });
  print("7");
}
复制代码

大家来猜一猜上面的打印效果,打印结果是:

7
1
6
3
5
2
4
复制代码

是不是没有想到。下面我们来分析下执行流程

  1. main 函数执行完的时候,f, f1, f2, f3 会进入事件队列,这里 print( 7 )不进入队列 直接打印 7;
  2. 队列里面又执行 f 事件,发现 f 存在一个打印事件 print(1) 直接打印 1;
  3. 现在又执行 f1 事件,发现了一个 f1.then(m) => print('6') 直接打印 6;
  4. 执行到了 f2 事件,发现了 f2 事件里面有一个 打印事件 3,先打印一个 3
    1. 然后遇见了一个新的时间 f4,放入新的事件队列里面。
    2. 现在又发现了一个 f1 的事件 print('5'),这里的 f1 因为执行完了 返回的 null 所以要把该事件存放入 微队列 里面。微队列里面优先执行所以打印 5 ;
  5. 现在该执行到了 f2 事件了,发现了一个 print(2), 直接打印 2;
  6. 最后执行到了 f4 事件,发现了一个 print(4),直接打印 4;

注意:这里的 f,f1,f2,f3... 进入事件队列里面要把它们理解成是一个整体,不管其它地方 还有 f1.then 或者 f2.then 。最后都是一个整体。

注意

  1. 使用 new Future 将任务加入 event 队列。

  2. Future 中的 then 并没有创建新的 Event 丢到 Event Queue 中,而只是一个普通的 Function Call ,

    FutureTask 执行完后,立即开始执行。

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

  4. 使用 Future.value 构造函数的时候,就会上一条一样,创建 Task 丢到 microtask Queue 中执行 then 传入的函数。

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

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

scheduleMicrotask();

看一段代码,然后再来分析它们的执行流程

//scheduleMicrotask
void testScheduleMicrotask() {
  //918346572
  scheduleMicrotask(() => print('s1'));

  new Future.delayed(new Duration(seconds: 1), () => print('s2'));

  new Future(() => print('s3')).then((_) {
    print('s4');
    scheduleMicrotask(() => print('s5'));
  }).then((_) => print('s6'));

  new Future(() => print('s10'))
      .then((_) => new Future(() => print('s11')))
      .then((_) => print('s12'));

  new Future(() => print('s7'));

  scheduleMicrotask(() => print('s8'));

  print('s9');
}
复制代码

打印结果

s9
s1
s8
s3
s4
s6
s5
s10
s7
s11
s12
s2
复制代码

这一次大家自己分析了 咳咳 ~ 。

注意

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

生成器

同步生成器

//同步生成器
//调用getSyncGenerator 立刻返回 Iterable
void main() {
  var it = getSyncGenerator(5).iterator;
  //  调用moveNext方法时getSyncGenerator才开始执行
  while (it.moveNext()) {
    print(it.current);
  }
}

//同步生成器: 使用sync*,返回的是Iterable对象
Iterable<int> getSyncGenerator(int n) sync* {
  print('start');
  int k = n;
  while (k > 0) {
    //yield会返回moveNext为true,并等待 moveNext 指令
    yield k--;
  }
  print('end');
}
复制代码

注意

  1. 使用 sync* ,返回的是 Iterable 对象。
  2. yield 会返回 moveNext 为 true ,并等待 moveNext 指令。
  3. 调用 getSyncGenerator 立即返回 Iterable 对象。
  4. 调用 moveNext 方法时 getSyncGenerator 才开始执行。

异步生成器

//异步生成器调用
// getAsyncGenerator立即返回Stream,只有执行了listen,函数才会开始执行
  StreamSubscription subscription = getAsyncGenerator(5).listen(null);
  subscription.onData((value) {
    print(value);
    if (value >= 2) {
      subscription.pause(); //可以使用StreamSubscription对象对数据流进行控制
    }
  });

//异步生成器: 使用async*,返回的是Stream对象
Stream<int> getAsyncGenerator(int n) async* {
  print('start');
  int k = 0;
  while (k < n) {
    //yield不用暂停,数据以流的方式一次性推送,通过StreamSubscription进行控制
    yield k++;
  }
  print('end');
}
复制代码

注意

  1. 使用 async* ,返回的是 Stream 对象。
  2. yield 不用暂停,数据以流的方式一次性推送,通过 StreamSubscription 进行控制。
  3. 调用 getAsyncGenerator 立即返回 Stream ,只有执行了 listen ,函数才会开始执行。
  4. listen 返回一个 StreamSubscription 对象进行流监听控制。
  5. 可以使用 StreamSubscription 对象对数据流进行控制。

递归生成器

异步

void main(){
      //异步
  getAsyncRecursiveGenerator(5).listen((value) => print(value));
}

//异步递归生成器
Stream<int> getAsyncRecursiveGenerator(int n) async* {
  if (n > 0) {
    yield n;
    yield* getAsyncRecursiveGenerator(n - 1);
  }
}

复制代码

同步

void main (){
  //递归生成器
  //同步
  var it1 = getSyncRecursiveGenerator(5).iterator;
  while (it1.moveNext()) {
    print(it1.current);
  }
}

//递归生成器:使用yield*
Iterable<int> getSyncRecursiveGenerator(int n) sync* {
  if (n > 0) {
    yield n;
    yield* getSyncRecursiveGenerator(n - 1);
  }
}
复制代码

注意

  1. yield* 以指针的方式传递递归对象,而不是整个同步对象。

隔离

Isolates

现代的浏览器以及移动浏览器都运行在多核 CPU 系统上。 要充分利用这些 CPU,开发者一般使用共享内存 数据来保证多线程的正确执行。然而, 多线程共享数据通常会导致很多潜在的问题,并导致代码运行出错。

所有的 Dart 代码在 isolates 中运行而不是线程。 每个 isolate 都有自己的堆内存,并且确保每个 isolate 的状态都不能被其他 isolate 访问。

元数据

(注解)-@deprecated

main() {
  dynamic tv = new Television();
  tv.activate();
  tv.turnOn();
}

class Television {
  @deprecated
  void activate() {
    turnOn();
  }

  void turnOn() {
    print('Television turn on!');
  }
}

复制代码

(注解)-@override

main() {
  dynamic tv = new Television();
  tv.activate();
  tv.turnOn();
  tv.turnOff();
}

class Television {
  @deprecated
  void activate() {
    turnOn();
  }
  
  void turnOn() {
    print('Television turn on!');
  }
  @override
  noSuchMethod(Invocation mirror) {
    print('没有找到方法');
  }
}

复制代码

注意

  1. 所有的 Dart 代码都可以使用: @deprecated 和 @override。

(注解)-自定义

创建 todo.dart 文件

//todo.dart

class Todo {
  final String who;
  final String what;

  const Todo({this.who, this.what});
}

复制代码
import 'todo.dart’;

main() {
  dynamic tv = new Television();
  tv.doSomething();
}

class Television {
  @Todo(who: 'damon', what: 'create a new method')
  void doSomething() {
    print('doSomething');
  }
}

复制代码

注意

  1. 在 java 中,如果自定义一个注解,需要添加 @Target 作用域注解,@Retention 注解类型注解,添加 @interface,然后定义注解参数。
  2. 构造方法定义为编译时常量

注释

单行注释

// 跟 Java 一样
复制代码

多行注释

/**跟 Java 一样**/
复制代码

文档注释

/// 跟 Java 一样
复制代码

总结

三篇基础 内容介绍了常见的 Dart 语言特性。 还有更多特性有待实现,但是新的特性不会破坏已有的代码。 更多信息请参考 Dart 语言规范 和 Effective Dart。

要了解 Dart 核心库的详情,请参考 Dart 核心库预览。

转载于:https://juejin.im/post/5c962b356fb9a0710e47e361

相关文章:

  • 阿里云ECS的1M带宽理解
  • 虚拟机中网络的设置与vim的安装
  • MySQL出现Waiting for table metadata lock的原因以及解决方法
  • 据说是微软面试题,答案让人泪奔==
  • 天梯赛题解 L1-006 连续因子
  • 麦当劳重金收购一大数据创业公司,持续加码数字化转型
  • 前嗅ForeSpider脚本教程:链接脚本
  • Chrome 控制台console的用法(学了之后对于调试js可是大大有用的哦)
  • ajax----tomact服务器运行
  • 软件测试工程师的核心竞争力是什么?
  • SQL-乐观锁,悲观锁之于并发
  • iOS:“Invalid top-level type in JSON write”引起程序闪退
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • 真正的反演笔记
  • 作为面试官的一点点感悟,谈谈技术人的成长之路
  • [PHP内核探索]PHP中的哈希表
  • 【React系列】如何构建React应用程序
  • Javascript基础之Array数组API
  • leetcode386. Lexicographical Numbers
  • leetcode讲解--894. All Possible Full Binary Trees
  • Magento 1.x 中文订单打印乱码
  • mongodb--安装和初步使用教程
  • MQ框架的比较
  • opencv python Meanshift 和 Camshift
  • Rancher如何对接Ceph-RBD块存储
  • Redash本地开发环境搭建
  • Web Storage相关
  • 多线程事务回滚
  • 翻译--Thinking in React
  • 给初学者:JavaScript 中数组操作注意点
  • 新版博客前端前瞻
  • 一、python与pycharm的安装
  • 找一份好的前端工作,起点很重要
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​520就是要宠粉,你的心头书我买单
  • ​Distil-Whisper:比Whisper快6倍,体积小50%的语音识别模型
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • ​如何防止网络攻击?
  • # Apache SeaTunnel 究竟是什么?
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • $.ajax()
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (动手学习深度学习)第13章 计算机视觉---微调
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (算法)Travel Information Center
  • (算法)前K大的和
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)德国人的记事本
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET 读取 JSON格式的数据