1 Dart是单线程的

开发中的耗时操作:
在开发中,我们经常会遇到一些耗时的操作需要完成,比如网络请求、文件读取等等;
如果我们的主线程一直在等待这些耗时的操作完成,那么就会进行阻塞,无法响应其它事件,比如用户的点击;显然,我们不能这么干!!

2 如何处理耗时的操作呢?

针对如何处理耗时的操作,不同的语言有不同的处理方式。

  • 多线程,比如Java、C++,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
  • 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。不过单线程如何能进行耗时的操作呢?!

其实它们并不冲突:
因为我们的一个应用程序大部分时间都是处于空闲的状态的,并不是无限制的在和用户进行交互。
比如等待用户点击、网络请求数据的返回、文件读写的IO操作,这些等待的行为并不会阻塞我们的线程;
这是因为类似于网络请求、文件读写的IO,我们都可以基于非阻塞调用;

  • 阻塞式调用: 调用结果返回之前,当前线程会被挂起,调用线程只有在得到调用结果之后才会继续执行。
  • 非阻塞式调用: 调用执行之后,当前线程不会停止执行,只需要过一段时间来检查一下有没有结果返回即可。

而我们开发中的很多耗时操作,都可以基于这样的 非阻塞式调用:

  • 比如网络请求本身使用了Socket通信,而Socket本身提供了select模型,可以进行非阻塞方式的工作;
  • 比如文件读写的IO操作,我们可以使用操作系统提供的基于事件的回调机制;

    3 事件循环

    单线程模型中主要就是在维护着一个事件循环(Event Loop)。
    事件循环就是将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中。不断的从事件队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。
    事件循环代码模拟: ```dart

RaisedButton( child: Text(‘Click me’), onPressed: () { final myFuture = http.get(‘https://example.com‘); myFuture.then((response) { if (response.statusCode == 200) { print(‘Success!’); } }); }, )

  1. 这些代码是如何放在事件循环中执行呢?
  2. 1. 当用户发生点击的时候,onPressed回调函数被放入事件循环中执行,执行的过程中发送了一个网络请求。
  3. 1. 网络请求发出去后,该事件循环不会被阻塞,而是发现要执行的onPressed函数已经结束,会将它丢弃掉。
  4. 1. 网络请求成功后,会执行then中传入的回调函数,这也是一个事件,该事件被放入到事件循环中执行,执行完毕后,事件循环将其丢弃。
  5. <a name="ijhRe"></a>
  6. # 4 Future
  7. Future只有两种状态, 未完成状态(uncompleted), 完成状态(completed
  8. <a name="w3PHX"></a>
  9. ## (1) 同步的网络请求
  10. ```dart
  11. import 'dart:io';
  12. void main(List<String> args) {
  13. print('main start');
  14. print(getNetworkData());
  15. print('main end');
  16. }
  17. String getNetworkData() {
  18. sleep(Duration(seconds: 3));
  19. return "newData";
  20. }

显然,上面的代码不是我们想要的执行效果,因为网络请求阻塞了main函数,那么意味着其后所有的代码都无法正常的继续执行。

(2) 异步的网络请求

  1. import 'dart:io';
  2. void main(List<String> args) {
  3. print('main start');
  4. print(getNetworkData());
  5. print('main end');
  6. }
  7. Future<String> getNetworkData() {
  8. return Future<String>(() {
  9. sleep(Duration(seconds: 3));
  10. return "newData";
  11. });
  12. }

执行结果如下:

main start Instance of ‘Future‘ main end // 这句之后等待了3秒 Exited

如何去获取请求到的结果呢? 通过then回调

  1. import 'dart:io';
  2. void main(List<String> args) {
  3. print('main start');
  4. var future = getNetworkData();
  5. future.then((value) {
  6. print(value);
  7. });
  8. print(future);
  9. print('main end');
  10. }
  11. Future<String> getNetworkData() {
  12. return Future<String>(() {
  13. sleep(Duration(seconds: 3));
  14. return "newData";
  15. });
  16. }

如果调用过程中出现了异常,拿不到结果,如何获取到异常的信息呢?

  1. import 'dart:io';
  2. void main(List<String> args) {
  3. print('main start');
  4. getNetworkData().then((val) {
  5. // 成功走这里
  6. print(val);
  7. }).catchError((err) {
  8. // 出现异常走这里
  9. print(err);
  10. }).whenComplete((){
  11. // 无论成功或失败都会走到这里
  12. });
  13. print('main end');
  14. }
  15. Future<String> getNetworkData() {
  16. return Future<String>(() {
  17. sleep(Duration(seconds: 3));
  18. return "newData";
  19. });
  20. }

(3) 链式调用

  1. import 'dart:io';
  2. void main(List<String> args) {
  3. print('main start');
  4. getNetworkData().then((value1) {
  5. print(value1);
  6. return "content data2";
  7. }).then((value2) {
  8. print(value2);
  9. return "message data3";
  10. }).then((value3) {
  11. print(value3);
  12. });
  13. print('main end');
  14. }
  15. Future<String> getNetworkData() {
  16. return Future<String>(() {
  17. sleep(Duration(seconds: 3));
  18. return "newData";
  19. });
  20. }

(4) Future.value

直接获取一个完成的Future,该Future会直接调用then的回调函数

  1. void main(List<String> args) {
  2. print('main start');
  3. Future.value("哈哈哈").then((value) {
  4. print(value);
  5. });
  6. print('main end');
  7. }

结果如下:

main function start main function end 哈哈哈

为什么立即执行,但是哈哈哈是在最后打印的呢?

这是因为Future中的then会作为新的任务会加入到事件队列中(Event Queue),加入之后你肯定需要排队执行了

(5) Future.delay(时间, 回调函数)

在延迟一定时间时执行回调函数,执行完回调函数后会执行then的回调;

  1. void main(List<String> args) {
  2. print('main start');
  3. Future.delayed(Duration(seconds: 3), () {
  4. return "3秒后的信息";
  5. }).then((value) {
  6. print(value);
  7. });
  8. print('main end');
  9. }

(6) Future.wait

Future.wait,它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调。

  1. Future.wait([
  2. // 2秒后返回结果
  3. Future.delayed(Duration(seconds: 2), () {
  4. return "hello";
  5. }),
  6. // 4秒后返回结果
  7. Future.delayed(Duration(seconds: 4), () {
  8. return " world";
  9. })
  10. ]).then((results){
  11. print(results[0]+results[1]);
  12. }).catchError((e){
  13. print(e);
  14. });

5 await 与 async

它们可以让我们用同步的代码格式,去实现异步的调用过程。
我们已经知道,Future可以做到不阻塞我们的线程,让线程继续执行,并且在完成某个操作时改变自己的状态,并且回调then或者errorCatch回调。

如何生成一个Future呢?

  • 通过我们前面学习的Future构造函数,或者后面学习的Future其他API都可以。
  • 还有一种就是用async声明函数。

    (1) 用async声明函数

    ```dart import ‘dart:io’;

void main(List args) { print(‘main start’);

getNetworkData().then((value) { print(value); });

print(‘main end’); }

Future getNetworkData() async { var result = await Future.delayed(Duration(seconds: 3), () { return “network data”; }); return “请求到的数据: “ + result; }

  1. <a name="zeKdf"></a>
  2. ## (2) 回调地狱
  3. 如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then回调中套回调情况。<br />比如现在有个需求场景是用户先登录,登录成功后会获得用户ID,然后通过用户ID,再去请求用户个人信息,获取到用户个人信息后,为了使用方便,我们需要将其缓存在本地文件系统
  4. ```dart
  5. //先分别定义各个异步任务
  6. Future<String> login(String userName, String pwd){
  7. ...
  8. //用户登录
  9. };
  10. Future<String> getUserInfo(String id){
  11. ...
  12. //获取用户信息
  13. };
  14. Future saveUserInfo(String userInfo){
  15. ...
  16. // 保存用户信息
  17. };

接下来,执行整个任务流:

  1. login("alice","******").then((id){
  2. //登录成功后通过,id获取用户信息
  3. getUserInfo(id).then((userInfo){
  4. //获取用户信息后保存
  5. saveUserInfo(userInfo).then((){
  6. //保存用户信息,接下来执行其它操作
  7. ...
  8. });
  9. });
  10. })

1) 使用Future消除Callback Hell

  1. login("alice","******").then((id){
  2. return getUserInfo(id);
  3. }).then((userInfo){
  4. return saveUserInfo(userInfo);
  5. }).then((e){
  6. //执行接下来的操作
  7. }).catchError((e){
  8. //错误处理
  9. print(e);
  10. });

2) 使用 async/await 消除 callback hell

通过Future回调中再返回Future的方式虽然能避免层层嵌套,但是还是有一层回调,有没有一种方式能够让我们可以像写同步代码那样来执行异步任务而不使用回调的方式?答案是肯定的,这就要使用async/await了

  1. task() async {
  2. try{
  3. String id = await login("alice","******");
  4. String userInfo = await getUserInfo(id);
  5. await saveUserInfo(userInfo);
  6. //执行接下来的操作
  7. } catch(e){
  8. //错误处理
  9. print(e);
  10. }
  11. }

6 Stream

Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。

  1. void main(List<String> args) async {
  2. Stream.fromFutures([
  3. // 1秒后返回结果
  4. Future.delayed(Duration(seconds: 1), () {
  5. return "hello 1";
  6. }),
  7. // 抛出一个异常
  8. Future.delayed(Duration(seconds: 2), () {
  9. throw AssertionError("Error");
  10. }),
  11. // 3秒后返回结果
  12. Future.delayed(Duration(seconds: 3), () {
  13. return "hello 3";
  14. })
  15. ]).listen((data) {
  16. print(data);
  17. }, onError: (e) {
  18. print(e.message);
  19. }, onDone: () {
  20. print("done");
  21. });
  22. }

image.png