Flutter 中的异步编程总结html
Dart 是一种单线程模型运行语言,其运行原理以下图所示:编程
Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另外一个叫作“事件队列” event queue。 从图中能够发现,微任务队列的执行优先级高于事件队列。bash
如今咱们来介绍一下Dart线程运行过程,如上图中所示,入口函数 main() 执行完后,消息循环机制便启动了。 首先会按照先进先出的顺序逐个执行微任务队列中的任务,当全部微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务, 如此循环往复,生生不息。微信
在Dart中,全部的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等,而微任务一般来源于Dart内部,而且微任务很是少, 之因此如此,是由于微任务队列优先级高,若是微任务太多,执行时间总和就越久,事件队列任务的延迟也就越久, 对于GUI应用来讲最直观的表现就是比较卡,因此必须得保证微任务队列不会太长。网络
在事件循环中,当某个任务发生异常并无被捕获时,程序并不会退出,而直接致使的结果是当前任务的后续代码就不会被执行了, 也就是说一个任务中的异常是不会影响其它任务执行的。并发
咱们能够看出,将任务加入到MicroTask中能够被尽快执行,但也须要注意,当事件循环在处理MicroTask队列时,Event队列会被卡住,应用程序没法处理鼠标单击、I/O消息等等事件。 同时,当事件循坏出现异常时,dart 中也能够经过 try/catch/finally 来捕获异常。app
基于上述理论,看一下任务调度less
import 'dart:async';
void myTask(){
print("this is my task");
}
void main() {
/// 1. 使用 scheduleMicrotask 方法添加
scheduleMicrotask(myTask);
/// 2. 使用Future对象添加
new Future.microtask(myTask);
}
复制代码
两个 task 都会被执行,控制台输出:异步
this is my task
this is my task
复制代码
import 'dart:async';
void myTask(){
print("this is my event task");
}
void main() {
new Future(myTask);
}
复制代码
控制台输出:async
this is my event task
复制代码
示例:
import 'dart:async';
void main() {
print("main start");
new Future((){
print("this is my task");
});
new Future.microtask((){
print("this is microtask");
});
print("main stop");
}
复制代码
控制台输出:
main start
main stop
this is microtask
this is my task
复制代码
能够看到,代码的运行顺序并非按照咱们的编写顺序来的,将任务添加到队列并不等于马上执行,它们是异步执行的,当前main方法中的代码执行完以后,才会去执行队列中的任务,且MicroTask队列运行在Event队列以前。
new Future.delayed(new Duration(seconds:1),(){
print('task delayed');
});
复制代码
使用 Future.delayed 可使用延时任务,可是延时任务不必定准
import 'dart:async';
import 'dart:io';
void main() {
var now = DateTime.now();
print("程序运行开始时间:" + now.toString());
new Future.delayed(new Duration(seconds:1),(){
now = DateTime.now();
print('延时任务执行时间:' + now.toString());
});
new Future((){
// 模拟耗时5秒
sleep(Duration(seconds:5));
now = DateTime.now();
print("5s 延时任务执行时间:" + now.toString());
});
now = DateTime.now();
print("程序结束执行时间:" + now.toString());
}
复制代码
输出结果:
程序运行开始时间:2019-09-11 20:46:18.321738
程序结束执行时间:2019-09-11 20:46:18.329178
5s 延时任务执行时间:2019-09-11 20:46:23.330951
延时任务执行时间:2019-09-11 20:46:23.345323
复制代码
能够看到,5s 的延时任务,确实实在当前程序运行以后的 5s 后获得了执行,可是另外一个延时任务,是在当前延时的基础上又延时执行的, 也就是,延时任务必须等前面的耗时任务执行完,才获得执行,这将致使延时任务延时并不许。
Dart类库有很是多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操做以后返回,好比像 IO操做。而不是等到这个操做完成。
Future与JavaScript中的Promise很是类似,表示一个异步操做的最终完成(或失败)及其结果值的表示。简单来讲,它就是用于处理异步操做的,异步处理成功了就执行成功的操做,异步处理失败了就捕获错误或者中止后续操做。一个Future只会对应一个结果,要么成功,要么失败。
因为自己功能较多,这里咱们只介绍其经常使用的API及特性。还有,请记住,Future 的全部API的返回值仍然是一个Future对象,因此能够很方便的进行链式调用。
建立方法: Future的几种建立方法
Future() Future.microtask() Future.sync() Future.value() Future.delayed() Future.error()
Future 和 Future.microtask 和 Future.delayed 上面已经演示过了。 Future.sync() 表示同步方法,会被当即执行: 如:
import 'dart:async';
void main() {
print("main start");
new Future.sync((){
print("sync task");
});
new Future((){
print("async task");
});
print("main stop");
}
复制代码
输出:
main start
sync task
main stop
async task
复制代码
当Future中的任务完成后,咱们每每须要一个回调,这个回调当即执行,不会被添加到事件队列。 如:
import 'dart:async';
void main() {
print("main start");
Future fut =new Future.value(18);
// 使用then注册回调
fut.then((res){
print(res);
});
// 链式调用,能够跟多个then,注册多个回调
new Future((){
print("async task");
}).then((res){
print("async task complete");
}).then((res){
print("async task after");
});
print("main stop");
}
复制代码
输出结果:
main start
main stop
18
async task
async task complete
async task after
复制代码
使用 catchError 捕获异常:
import 'dart:async';
void main() {
print("main start");
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print("success");
}).catchError((e){
//执行失败会走到这里
print(e);
});
print("main stop");
}
复制代码
输出:
main start
main stop
Assertion failed
复制代码
在本示例中,咱们在异步任务中抛出了一个异常,then的回调函数将不会被执行,取而代之的是 catchError回调函数将被调用;可是,并非只有 catchError回调才能捕获错误,then方法还有一个可选参数onError,咱们也能够它来捕获异常:
import 'dart:async';
void main() {
print("main start2");
Future.delayed(new Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
print("main stop2");
}
复制代码
结果:
main start2
main stop2
Assertion failed
复制代码
whenComplete 必定获得执行:
import 'dart:async';
void main() {
print("main start3");
Future.delayed(new Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print(data);
}).catchError((e){
//执行失败会走到这里
print(e);
}).whenComplete((){
//不管成功或失败都会走到这里
print("this is the end...");
});
print("main stop3");
}
复制代码
结果:
main start3
main stop3
Assertion failed
this is the end...
复制代码
Future.wait 表示多个任务都完成以后的回调。
import 'dart:async';
void main() {
print("main start4");
Future.wait([
// 2秒后返回结果
Future.delayed(new Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回结果
Future.delayed(new Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
print("main stop4");
}
复制代码
结果:
main start4
main stop4
hello world
复制代码
不少时候咱们会依赖一些异步数据来动态更新UI,好比在打开一个页面时咱们须要先从互联网上获取数据,在获取数据的过程当中咱们显式一个加载框,等获取到数据时咱们再渲染页面;又好比咱们想展现Stream(好比文件流、互联网数据接收流)的进度。固然,经过StatefulWidget咱们彻底能够实现上述这些功能。 但因为在实际开发中依赖异步数据更新UI的这种场景很是常见,所以Flutter专门提供了FutureBuilder和StreamBuilder两个组件来快速实现这种功能。
FutureBuilder会依赖一个Future,它会根据所依赖的Future的状态来动态构建自身。咱们看一下FutureBuilder构造函数:
FutureBuilder({
this.future,
this.initialData,
@required this.builder,
})
复制代码
future:FutureBuilder依赖的Future,一般是一个异步耗时任务。
initialData:初始数据,用户设置默认数据。
builder:Widget构建器;该构建器会在Future执行的不一样阶段被屡次调用,构建器签名以下:
Function (BuildContext context, AsyncSnapshot snapshot)
复制代码
snapshot会包含当前异步任务的状态信息及结果信息 ,好比咱们能够经过snapshot.connectionState获取异步任务的状态信息、经过snapshot.hasError判断异步任务是否有错误等等,完整的定义读者能够查看AsyncSnapshot类定义。
示例:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<String> mockNetworkData() async {
return Future.delayed(Duration(seconds: 2), () => "这是网络请求的数据。。。");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: FutureBuilder<String>(
future: mockNetworkData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// 请求已结束
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
// 请求失败,显示错误
return Text("Error: ${snapshot.error}");
} else {
// 请求成功,显示数据
return Text("Contents: ${snapshot.data}");
}
} else {
// 请求未结束,显示loading
return CircularProgressIndicator();
}
},
),
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
复制代码
效果:
在Dart1.9中加入了async和await关键字,有了这两个关键字,咱们能够更简洁的编写异步代码,而不须要调用Future相关的API 将 async 关键字做为方法声明的后缀时,具备以下意义
// 导入io库,调用sleep函数
import 'dart:io';
// 模拟耗时操做,调用sleep函数睡眠2秒
doTask() async{
await sleep(const Duration(seconds:2));
return "Ok";
}
// 定义一个函数用于包装
test() async {
var r = await doTask();
print(r);
}
void main(){
print("main start");
test();
print("main end");
}
复制代码
结果:
main start
main end
Ok
复制代码
Stream 也是用于接收异步事件数据,和Future 不一样的是,它能够接收多个异步操做的结果(成功或失败)。 也就是说,在执行异步任务时,能够经过屡次触发成功或失败事件来传递结果数据或错误异常。 Stream 经常使用于会屡次读取数据的异步任务场景,如网络内容下载、文件读写等。
void main(){
print("main start");
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(new Duration(seconds: 1), () {
return "hello 1";
}),
// 抛出一个异常
Future.delayed(new Duration(seconds: 2),(){
throw AssertionError("Error");
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return "hello 3";
})
]).listen((data){
print(data);
}, onError: (e){
print(e.message);
},onDone: (){
});
print("main end");
}
复制代码
结果:
main start
main end
hello 1
Error
hello 3
复制代码
咱们知道,在Dart中Stream 也是用于接收异步事件数据,和Future 不一样的是,它能够接收多个异步操做的结果,它经常使用于会屡次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展现流上事件(数据)变化的UI组件。 下面看一下StreamBuilder的默认构造函数:
StreamBuilder({
Key key,
this.initialData,
Stream<T> stream,
@required this.builder,
})
复制代码
能够看到和FutureBuilder的构造函数只有一点不一样:前者须要一个future,然后者须要一个stream。
示例:
Stream<int> counter() {
return Stream.periodic(Duration(seconds: 1), (i) {
return i;
});
}
复制代码
Widget buildStream (BuildContext context) {
return StreamBuilder<int>(
stream: counter(), //
//initialData: ,// a Stream<int> or null
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('没有Stream');
case ConnectionState.waiting:
return Text('等待数据...');
case ConnectionState.active:
return Text('active: ${snapshot.data}');
case ConnectionState.done:
return Text('Stream已关闭');
}
return null; // unreachable
},
);
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body:Center(
child: buildStream(context),
)
// This trailing comma makes auto-formatting nicer for build methods.
);
复制代码
效果:
Dart是基于单线程模型的语言。可是在开发当中咱们常常会进行耗时操做好比网络请求,这种耗时操做会堵塞咱们的代码,因此在Dart也有并发机制,名叫isolate。 APP的启动入口main函数就是一个相似Android主线程的一个主isolate。和Java的Thread不一样的是,Dart中的isolate没法共享内存,相似于Android中的多进程。
spawnUri方法有三个必须的参数,第一个是Uri,指定一个新Isolate代码文件的路径,第二个是参数列表,类型是List,第三个是动态消息。 须要注意,用于运行新Isolate的代码文件中,必须包含一个main函数,它是新Isolate的入口方法,该main函数中的args参数列表, 正对应spawnUri中的第二个参数。如不须要向新Isolate中传参数,该参数可传空List 主Isolate中的代码:
import 'dart:isolate';
void main() {
print("main isolate start");
create_isolate();
print("main isolate stop");
}
// 建立一个新的 isolate
create_isolate() async{
ReceivePort rp = new ReceivePort();
///建立发送端
SendPort port1 = rp.sendPort;
///建立新的 isolate ,并传递了发送端口。
Isolate newIsolate = await Isolate.spawnUri(new 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 文件的同级路径下,新建一个 other_task.dart ,内容以下:
import 'dart:isolate';
import 'dart:io';
///接收到了发送端口。
void main(args, SendPort port1) {
print("isolate_1 start");
print("isolate_1 args: $args");
ReceivePort receivePort = new ReceivePort();
SendPort 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,输出结果:
main isolate start
main isolate stop
isolate_1 start
isolate_1 args: [hello, isolate, this is args]
main isolate message: [0, SendPort]
isolate_1 stop
main isolate message: [1, isolate_1 任务完成]
isolate_1 message: [1, 这条信息是 main isolate 发送的]
复制代码
isolate 之间的通讯时经过 ReceivePort 来完成的,ReceivePort 能够理解为一根水管,消息的传递方向时固定的,把这个水管的发送端发送给对方, 对方就能经过这个水管发送消息。
除了使用spawnUri,更经常使用的是使用spawn方法来建立新的Isolate,咱们一般但愿将新建立的Isolate代码和main Isolate代码写在同一个文件, 且不但愿出现两个main函数,而是将指定的耗时函数运行在新的Isolate,这样作有利于代码的组织和代码的复用。spawn方法有两个必须的参数, 第一个是须要运行在新Isolate的耗时函数,第二个是动态消息,该参数一般用于传送主Isolate的SendPort对象。
示例:
import 'dart:isolate';
import 'dart:io';
void main() {
print("主程序开始");
create_isolate();
print("主程序结束");
}
// 建立一个新的 isolate
create_isolate() async{
ReceivePort rp = new ReceivePort();
SendPort port1 = rp.sendPort;
Isolate newIsolate = await Isolate.spawn(doWork, port1);
SendPort port2;
rp.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");
ReceivePort rp2 = new ReceivePort();
SendPort 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");
}
复制代码
结果:
主程序开始
主程序结束
new isolate start
main isolate message: [0, SendPort]
new isolate end
main isolate message: [1, doWork 任务完成]
doWork message: [1, 这条信息是 main isolate 发送的]
复制代码
参考:
juejin.im/post/5cdbf2… juejin.im/post/5c876e… book.flutterchina.club/chapter2/th… book.flutterchina.club/chapter7/fu… book.flutterchina.club/chapter1/da…
欢迎关注「Flutter 编程开发」微信公众号 。