dart异步

431 阅读10分钟

Dart异步

我们知道,dart是单线程语言.他没有多个线程的概念让我们写代码时单独创建一个线程来执行任务.但是它仍然支持用Future Stream以及async await等让我们进行异步编程.这里我们从dart支持异步编程的基础Isolate和eventLoop开始.介绍一下dart异步任务执行的过程.

课程大纲

Isolates 和 EventLoops

什么是Isolate

  • Isolates是运行所有dart代码的地方,他就像内存中的一个小空间,有自己的内存块,和运行事件循环(EventLoop).
  • 许多dart应用在单一Isolate中运行所有代码(一个dart只有一个Isolate,flutter的root isolate会负责渲染和Ui交互),但如果你的计算量特别庞大,它在主Isolate中执行会导致当前运行卡顿.那么这时,可以使用多个Isolate.

如何创建Isolate

dart的代码都是默认跑在root isolate上的,那么如何创建一个isolate呢?接下来我们就自己创建一个isolate!

1.Isolate.spawn 方式创建Isolate

import 'dart:io';
import 'dart:isolate';
// 1. 创建Isolate
void main(){
  print('main isolate start');
  createIsolate();
  print('main isolate end');
}

//创建子isolate函数
void createIsolate() async{
  //Isolate之间唯一的通信方式.
  var rPort=ReceivePort();
  var sPort=rPort.sendPort;
  //创建新的isolate,并注册耗时任务,和与之通信的sendPort
  var isolate =await Isolate.spawn(doWork,sPort);

  SendPort port2;
  //获得子isolate返回的数据,然后给子isolate反馈.
  rPort.listen((message){
    print('main isolate message: $message');
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,'这条信息是 main isolate 发送的']);
    }
  });
}
// 处理耗时任务
void doWork(SendPort port1){
  print('new isolate start');
  var rp2 = ReceivePort();
  var port2 = rp2.sendPort;
  rp2.listen((message){
    print('doWork message: $message');
  });
  // 将新isolate中创建的SendPort发送到主isolate中用于通信
  port1.send([0, port2]);
  // 模拟耗时5秒
  sleep(Duration(seconds:5));
  port1.send([1, 'doWork 任务完成']);

  print('new isolate end');
}

1.Isolate.spawnUrl 方式创建isolate

主isolate文件

import 'dart:isolate';


void main() {
  print('main isolate start');
  create_isolate();
  print('main isolate stop');
}

// 创建一个新的 isolate
void create_isolate() async{
  var rp = ReceivePort();
  var port1 = rp.sendPort;
  var newIsolate = await Isolate.spawnUri(Uri(path: './other_task.dart'), ['hello, isolate', 'this is args'], port1);
  SendPort port2;
  rp.listen((message){
    print('main isolate message: $message');
    if (message[0] == 0){
      port2 = message[1];
    }else{
      port2?.send([1,'这条信息是 main isolate 发送的']);
    }
  });
  // 可以在适当的时候,调用以下方法杀死创建的 isolate
  // newIsolate.kill(priority: Isolate.immediate);
}

子isolate文件

import 'dart:isolate';
import  'dart:io';


void main(args, SendPort port1) {
  print('isolate_1 start');
  print('isolate_1 args: $args');
  var receivePort = ReceivePort();
  var port2 = receivePort.sendPort;
  receivePort.listen((message){
    print('isolate_1 message: $message');
  });
  // 将当前 isolate 中创建的SendPort发送到主 isolate中用于通信
  port1.send([0, port2]);
  // 模拟耗时5秒
  sleep(Duration(seconds:5));
  port1.send([1, 'isolate_1 任务完成']);
  print('isolate_1 stop');
}

两者都会创建一个单独的Isolate来进行运算.从而减轻主Isolate的运算.让他更多的关心处理组件重建与和用户的交互等事件.新的Isolate将获得自己的时间循环和自己的内存.即使原始的Isolate是这个新Isolate的母体,原始Isolate也不能访问他,这些小空间彼此独立,互相隔离.他们之间沟通的方式就是彼此之间来回传递消息.一个Isolate会向另个一Isolate发送消息,接收到消息的Isolate会使用他们的事件循环处理消息.缺乏内存共享看起来是有些严格,但是对Dart编码器有一些关键性的好处,比如Isolate的内存分配和垃圾回收不需要锁定,他只有一个线程.这对Flutter应用程序非常有用,它有时需要快速构建和拆除一堆小部件.

Flutter中创建Isolate

2.cumpute函数

import 'package:flutter/foundation.dart';
import  'dart:io';

// 创建一个新的Isolate,在其中运行任务doWork
create_new_task() async{
  var str = "New Task";
  var result = await compute(doWork, str);
  print(result);
}


String doWork(String value){
  print("new isolate doWork start");
  // 模拟耗时5秒
  sleep(Duration(seconds:5));

  print("new isolate doWork end");
  return "complete:$value";
}

Isolate使用建议

isolate虽然可以单独创建并执行复杂的异步任务,但是我们仍需要以dart的事件循环为主来处理我们的异步任务,这样才能更好的发挥dart的语言优势.

那么应该在什么时候使用Future,什么时候使用Isolate呢?一个最简单的判断方法是根据某些任务的平均时间来选择:

  • 方法执行耗时在几毫秒或者几十毫秒以下的,我们应该用Future
  • 方法执行耗时在百毫秒以上的我们可以采用Isolate 另外我们还可以按照一些场景分类来区分是应该使用Future还是Isolate
  • 网络请求
  • 文件操作
  • json解析
  • 加密解密等

什么是EventLoop

什么是eventLoop?我们先开看一个图片

  • 如上图所示,dart应用程序从main方法开始启动,然后执行微任务队列的任务,执行完成后执行事件队列的任务.待所有微任务及事件执行完成后我们的eventloop就算执行完成了.然后等待新的事件到来.

  • eventloop需要执行应用程序的所有小事件,比如磁盘的I/O或者用户的手指点击各种事件等.应用程序无法预测这些事件会何时或以何种顺序发生,而且它必须使用永不阻塞的单线程来处理所有事件.因此它以一个事件循环方式运行.从事件队列中取得最先放进去的事件处理完成后又继续处理下一个事件.依次类推直到事件队列清空为止.

  • 在整个应用程序运行过程中,你点击屏幕 下载内容 计时器运行等,事件循环不断运作.每当动作中断时线程就会等待下一个事件.dart所有高级Api都可以用于异步编程,Future,Stream,async,await,它们的工作全部都基于这个简单的循环之上.

使用顶级函数 scheduleMicrotask(void callback()) 将创建一个新的微任务添加到微任务队列的队尾

import 'dart:async';
void main(){
  scheduleMicrotask((){
	//some other code
  });
}

使用Future.microtask(FutureOr computation())将任务添加到微任务队列的队尾

import 'dart:async';
void main(){
  Future.microtask(() => {});
}

Future

Future是什么

Future是dart的异步的一个api,当dart语句执行到返回future的函数时他不会等待此函数内的任务全都执行完毕在执行下一条语句.而是跳过这句,进行一定的操作之后,继续执行下面的语句.

Future状态

  1. 未完成状态
  2. 完成状态 whenComplete
  3. 异常状态 catchError

下面我们来创建一个简单的Future并进行一些简单常用的操作

import 'dart:async';
void main(){
  Future((){
    print('future start!');
    return 'some string';
  }).then((value) => print(value))
  .catchError((onError)=>print(onError))
  .whenComplete(() => print('完成'));
}

如何构建一个Future

  1. Future.then(), 用来注册一个Future完成时要调用的回调。如果 Future 有多个then,它们也会按照链接的先后顺序同步执行,注意它们会共用一个event loop。
Future((){
  return "任务5";
}).then((value) => print(value+"结束"));
  1. Future.value(),创建返回指定value的future
Future.value(1).then((value)=>print(value));
//输出值 1
  1. Future.delayed(), 创建一个延迟执行的future
    注意此延迟和sleep的区别sleep会让当前Isolate阻塞,放在main方法中时此sleep会阻塞mian方法执行完成,再去执行其他操作
Future.delayed(Duration(seconds: 1)).then((value) => print("延迟执行"));
//输出值
延迟执行
  1. Future.timeout, 超时会抛出TimeoutException异常处理
Future.delayed(new Duration(seconds: 2), () {
      return 1;
    }).timeout(new Duration(seconds:1)).then(print).catchError(print);
  1. Future.foreach,根据某个集合创建一系列的Future,并且按着顺序执行这些Future
Future.forEach({1,2,3}, (num){
  return Future.delayed(Duration(seconds: num),(){print("第$num秒执行");});
});
  1. Future.wait(), 等待多个Future任务完成.
  • 所有的future都有正常结果返回,使用then监听的方法获的是一个结果的集合
  • 其中一个或者多个Future发生错误,产生error,则Future返回的结果就是第一个发生错误的Future的值
void testFuture4() async {
  var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
  var future2 = new Future.delayed(new Duration(seconds: 2), () => 1);
  var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);
  Future.wait([future1,future2,future3])
  	.then((value) =>print(value))                                           
    	.catchError((error)=>print(error));
 }
//输出值
[1, 5, 3]

void testFuture5() async {
  var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
  var future2 = new Future.delayed(new Duration(seconds: 2), () => throw 'Future 发生错误啦!');
  var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);
  Future.wait([future1,future2,future3]).then((value) =>print(value))                                           
    .catchError((error)=>print(error));
 }
//输出值
Future 发生错误啦!
  1. Future.any 返回的是第一个执行完成的Future的结果,不管结果是正确还是错误
Future
    .any([1, 2, 5].map((delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
    .then(print)
    .catchError(print);
  }
///输出值
1
  1. Future.doWhile,重复执行某一个动作,直到返回false,退出循环,适用一些递归的操作
var random = new Random();
var totalDelay = 0;
Future.doWhile(() {
  if (totalDelay > 10) {
    print('total delay: $totalDelay seconds');
    return false;
  }
  var delay = random.nextInt(5) + 1;
  totalDelay += delay;
  return new Future.delayed(new Duration(seconds: delay), () {
    print('waited $delay seconds');
    return true;
  });
}).then(print).catchError(print);
  1. Future.sync 同步执行,然后调度到microtask queue来完成自己,也就是一个阻塞任务,会阻塞当前线程,sync执行完成了之后代码才会走到下一行
void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future.sync(() {
        print("Future sync event 2");
    });
    Future((){
        print("Future event 3");
    });
    Future.microtask((){
        print("microtask event");
    });
}
//输出值
Future sync event 2
microtask event
Future event 1
Future event 3
  1. Future.microtask 创建一个在microtask queue运行的Future。 microtask queue的优先级要比event queue高,而一般Future是在event queue执行的,所以Future.microtask创建的Future会优先于其他的Future执行:
void testFuture() async {
    Future((){
        print("Future event 1");
    });
    Future((){
        print("Future event 2");
    });
    Future.microtask((){
        print("microtask event");
    });
}
///输出值
microtask event
Future event 1
Future event 2

FutureBuilder介绍

  • 它是Flutter SDK附带的一个小组件,初始化时需要一个Future和一个构建器方法.
  • FutureBuilder是一个将异步操作和异步UI更新结合在一起的类,通过它我们可以将网络请求,数据库读取等的结果更新的页面上。
  • 当Futrue完成时,他会自动重建他的子节点.它通过调用其构建器方法来实现该方法会获取Future当前状态的环境和快照,你可以检查快照,查看Future是否以抛出异常的方式完成并进行处理.
  • 你还可以检查hasData属性.查看它是否以带有一个值的方式完成.如果没有你知道你还要再等等.你也可以输出一些东西.即使是在Flutter代码中你也可以看到这三种状态不断出现.未完成,完成并返回值,完成并抛出异常
String showResult = '';
Future<CommonModel> fetchPost() async {
  final response = await http .get('http://www.devio.org/io/flutter_app/json/test_common_model.json');
  Utf8Decoder utf8decoder = Utf8Decoder(); //fix 中文乱码
  var result = json.decode(utf8decoder.convert(response.bodyBytes));
  return CommonModel.fromJson(result);
}


body: FutureBuilder<CommonModel>(
    future: fetchPost(),
    builder:
        (BuildContext context, AsyncSnapshot<CommonModel> snapshot) {
      switch (snapshot.connectionState) {
        case ConnectionState.none:
          return new Text('Input a URL to start');
        case ConnectionState.waiting:
          return new Center(child: new CircularProgressIndicator());
        case ConnectionState.active:
          return new Text('');
        case ConnectionState.done:
          if (snapshot.hasError) {
            return new Text(
              '${snapshot.error}',
              style: TextStyle(color: Colors.red),
            );
          } else {
            return new Column(children: <Widget>[
              Text('icon:${snapshot.data.icon}'),                  
              Text('statusBarColor:${snapshot.data.statusBarColor}'),
              Text('title:${snapshot.data.title}'),
              Text('url:${snapshot.data.url}')
            ]);
          }
      }
    }),

Stream

Stream是一个比较难以理解的概念,我们可以参照同步中的int值与int迭代器之间的关系理解Future与Sream,Stream就像是异步中的迭代器

SyncintIterator < int>
AsyncFutureStream

同样根据stream的字面意思及工作方式,我们可以把Stream当作一个河流,上游往河道里添加东西,下游从河道中取出东西.

流的种类

流有两种

单订阅流("Single-subscription" streams)

  • 单个订阅流在流的整个生命周期内仅允许有一个listener。它在有收听者之前不会生成事件,并且在取消收听时它会停止发送事件,即使你仍然在Sink.add更多事件。
  • 即使在第一个订阅被取消后,也不允许在单个订阅流上进行两次侦听。
  • 单订阅流通常用于流式传输更大的连续数据块,如文件I / O.
StreamController controller = StreamController();

controller.stream.listen((data)=> print(data));
controller.stream.listen((data)=> print(data));

controller.sink.add(123);
//此时报错  Bad state: Stream has already been listened to.

多订阅流("Broadcast" streams)

  • 广播流允许任意数量的收听者,且无论是否有收听者,他都能产生事件。所以中途进来的收听者将不会收到之前的消息。
  • 如果多个收听者想要收听单个订阅流,请使用asBroadcastStream在非广播流之上创建广播流。
  • 如果在触发事件时将收听者添加到广播流,则该侦听器将不会接收当前正在触发的事件。如果取消收听,收听者会立即停止接收事件。
  • 一般的流都是单订阅流。从Stream继承的广播流必须重写isBroadcast 才能返回true。
StreamController controller = StreamController();
//将单订阅流转化为广播流
Stream stream = controller.stream.asBroadcastStream();

stream.listen((data)=> print(data));
stream.listen((data)=> print(data));

controller.sink.add(123);

输出:
123
123

Stream的创建方式

  1. 使用async关键字创建流 是使用异步生成器(async)函数创建新流。流是在调用函数时创建的,当侦听流时,函数的主体开始运行。当函数返回时,流关闭。在函数返回之前,它可以使用yield或yield*语句在流上发出事件。 下面是一个基本的示例,它定期发出数字.
Stream<int> timedCounter(Duration interval, [int maxCount]) async* {
  int i = 0;
  while (true) {
    await Future.delayed(interval);
    yield i++;
    if (i == maxCount) break;
  }
}

main(List<String> args) async{
  var stream = timedCounter(Duration(seconds: 2), 5);
  await for (var i in stream) {
    print(i);
  }
}
  1. 使用StreamController来创建流
//任意类型的流
StreamController controller = StreamController();
controller.sink.add(123);
controller.sink.add("xyz");
controller.sink.add(Anything);

//创建一条处理int类型的流
StreamController<int> numController = StreamController();
numController.sink.add(123);
  1. 通过创建Stream的函数 Stream 有三个构建方法:
  • Stream.fromFuture:从Future创建新的单订阅流,当future完成时将触发一个data或者error,然后使用Down事件关闭这个流。
void main(){
  Stream.fromFuture(
      Future((){throw NullThrownError();})
          .then((value){print(value);print('111');})
          // .catchError((e){print(e);print('222');})
  ).listen(
          (v){print(v);print('dddd');},
      onError: (e){print(e);print('sss');});
}
  • Stream.fromFutures:从一组Future创建一个单订阅流,每个future都有自己的data或者error事件,当整个Futures完成后,流将会关闭。如果Futures为空,流将会立刻关闭。
  • Stream.fromIterable:创建从一个集合中获取其数据的单订阅流。
Stream.fromIntreable([1,2,3]);
  1. IO Stream

向流里添加数据

controller.sink.add(123);

Stream的订阅

stream.stream.listen((event) { },onError:(error) {},onDone:(){},cancelOnError: true);

监听一个流最常见的方法就是listen。当有事件发出时,流将会通知listener。Listen方法提供了这几种触发事件:

  • onData(必填):收到数据时触发
  • onError:收到Error时触发
  • onDone:结束时触发
  • cancelOnError:遇到第一个Error时是否取消订阅,默认为false

Stream上的具体操作

stream可以暂停,恢复,和取消.

var stream = StreamController();
  var subscription = stream.stream.listen((event) {},onError:(error) {},onDone:(){},cancelOnError: true);
  subscription.pause();
  subscription.resume();
  subscription.cancel();

截取Stream中的部分data

where

where 过滤出符合 where 中条件的数据

var data = [1,2,3,4,5]; // 集合数据
var stream = new Stream.fromIterable(data);
stream
    .where((value) => value % 2 == 0) // 判断value对2取余等于0
    .listen((value) => print("where: $value"));
    
输出:
where 2
where 4

take

take 提取出Stream中的前X个数据

stream
    .take(3) // 取出Stream中前3个元素
    .listen((value) => print("take: $value"));
输出:
take: 1
take: 2
take: 3

takeWhile

takeWhile 只要takeWhile中的条件为true,则将此数据加入到Stream中

stream
    .takeWhile((value) => value < 3) // 取出Stream中小于3的所有数据
    .listen((value) => print("takeWhile: $value"));

skip

skip 跳过Stream中的前X个数据

stream
    .skip(3)  // 跳过Stream中的前3个数据
    .listen((value) => print("skip: $value"));

skipWhile

skipWhile 只要skipWhile中的条件为true,则跳过此数据

stream
    .skipWhile((value) => value < 3) // 跳过所有值小于3的数据
    .listen((value) => print("skipWhile: $value"));

输出:
skipWhile: 3
skipWhile: 4
skipWhile: 5

distinct

distinct 去除Stream中重复的数据

StreamController streamController = StreamController();
  
  streamController.sink.add(1);
  streamController.sink.add(2);
  streamController.sink.add(2);
  streamController.sink.add(3);
  
Stream newStream = streamController.stream.distinct();  
  
newStream.listen((value) {
    print("distinct: $value");
  });

输出:
distinct: 1
distinct: 2
distinct: 3

将Stream中的data进行转换

map

map 数据转换

// 在元数据的基值上做 +1 操作
Stream newStream = stream.map((value) => value + 1);  
  
newStream.listen((value) {
    print("map : $value");
  });
输出
map : 2
map : 3
map : 4
map : 5
map : 6

校验Stream中的data

any

any 只要Stream中有一个数据满足条件则返回 true校验Stream中的data

stream
    .any((value) => value < 5)
    .then((result) => print("Stream中有数据 < 5 返回 $result"));
 输出:
 Stream中有数据 < 5 : true

every 判断Stream中所有的数据满足某条件

broadcastStream
    .every((value) => value < 5)
    .then((result) => print("所有数据都 < 5 返回 $result")); 
输出:
所有数据都 < 5 返回 false

contains

contains 判断Stream中是否包含某数据

broadcastStream
    .contains(4)
    .then((result) => print("Stream中包含 4 返回 $result")); // true

Async And Await 关键字构成的dart异步

Async 和 Await只是Future和Stream的替代语法,可以帮助我们编写更清晰,更易读的代码.使用他们可以写出看起来像同步的异步代码.

假如我们需要请求服务器上的一个用户信息,需要先从内存卡中取出userId,然后再根据userId去网络请求用户信息.

//假设我们有这两个方法
Future<int> _getUserIdFromDist(){}//从SDCard中获取用户Id
Future<String> _getUserInfoFromNetWork(int userId){}//根据用户Id从网络中获取用户信息

eg:

同步代码,也是我们最容易理解的代码,我们会这样写

String getUserInfo(){
    int id=_getUserIdFromDist();
    String userInfo=_getUserInfoFromNetWork(id);
    return userInfo;
}

使用Future

Future<String> getUserInfo(){
	return _getUserIdFromDist().then((id){
    	return _getUserInfoFromNetWork(id);
    }).then((userInfo){
    	return userInfo;
    });
}

使用 async await 关键词

Future<String> getUserInfo() async{
  try{
    var id = await _getUserIdFromDist();
    var userInfo = await _getUserInfoFromNetWork(id);
    return userInfo;
  } on HttpException catch(err){
  	print('network error :$err')
    return '';
  } finaly{
  	println('All done!');
  }

}

yield* 关键字


import 'dart:async';
 
main(List<String> args) async {
  await for (int i in numbersDownFrom(10)) {
    print('$i apples');
  }
}
 
Stream numbersDownFrom(int n) async* {
  if (n >= 0) {
    await new Future.delayed(new Duration(milliseconds: 100));
    yield n;
    yield* numbersDownFrom(n - 1);
  }
}

将子序列插入到当前序列中,常用于优化递归.

The yield* (pronounced yield-each) statement. The expression following yield* must denote another (sub)sequence. What yield* does is to insert all the elements of the subsequence into the sequence currently being constructed, as if we had an individual yield for each element.

(yield(发音为yield each)语句。yield后面的表达式必须表示另一个(子)序列。yield的作用是将子序列的所有元素插入到当前正在构造的序列中,就像我们对每个元素都有一个单独的yield一样。)*

参见:

习题文章

Future处理和创建详细流程

dart异步官方教程

Future的执行顺序 课后习题

import 'dart:async';

void main() {
  Future future1 = Future(() => print('future1'));
  Future future2 = Future(() => print('future2'));
  Future future3 = Future(() => print('future3'));
  Future future4 = Future(() => print('future4'));

  print('main 1');

  Future future5 = Future.value("future5").then((onValue) {
    print(onValue);
    print('future5.then');
  }).whenComplete(() {
    print('future5 complete');
  });

  Future future6 = Future.value(future1).then((_) {
    print('future6.then');
  }).whenComplete(() {
    print('future6 complete');
  });

  future4.then((_) {
    print('future4.then');
  });

  Future future7 = Future.sync(() {
    print('future7');
  }).then((_) {
    print('future7.then');
  }).whenComplete(() {
    print('future7 complete');
  });

  future3.then((_) {
    print('future3.then1');
    future2.then((_) {
      print('future2.then');
    });
    scheduleMicrotask((){print('scheduleMicrotask on future3.then1');});
  }).then((_) => print('future3.then2'));

  print('main 2');

  future1.then((_) {
    print('future1.then');
  });

  future5.then((_) {
    print('future5.then2');
  });
}

习题答案 :


main 1
future7
main 2
future5
future5.then
future5 complete
future5.then2
future7.then
future7 complete
future1
future6.then
future6 complete
future1.then
future2
future3
future3.then1
future3.then2
future2.then
scheduleMicrotask on future3.then1
future4
future4.then

Process finished with exit code 0

思考:在dart中如何同时发出多个请求增加网络效率?

void main(){
  print(DateTime.now());
  Future.wait([
    Future.delayed(Duration(seconds: 2),(){return '10a';}),
    Future.delayed(Duration(seconds: 1),(){return 11;}),
    Future.delayed(Duration(seconds: 3),(){return 12;})]).
  then((value) => print('$value+${DateTime.now().toString()}'));
}