Future 类,其表示一个 T 类型的异步操作结果。如果异步操作不需要结果,则类型为 Future。也就是说首先Future是个泛型类,可以指定类型。如果没有指定相应类型的话,则Future会在执行动态的推导类型。
在网络请求中经常使用异步编程,那么来探索一下Flutter 中的异步编程。
dart是单线程的,所以底层没有锁之类的东西,但是这不代表着dart不能异步,异步不代表多线程。
下面代码运行后发现做其他事情被堵塞住了,这里async不起作用是因为需要搭配Future使用。
将耗时操作使用Future包装起来,这里可以看到做其他事情就不会被堵塞住了,那么现在即使方法不加async也是异步的,因为Future里面已经是异步的了。或者说async不会异步执行,Future才会异步执行。
如果把 print(‘结束data = $_data’); 放在Future外面那么其就会先于Future里面的代码执行。
那么如何让等待Future里面的执行完在执行后面代码呢?这时候需要用到await。加了await之后,后面的代码就会等待Future里面的执行完之后在执行。这里的await需要搭配async使用,而没有await的情况下,async是没有意义的。await后面的操作也需要是异步的。那么也就是说,Future是用来异步执行的,而async和await搭配是用来让Future里面的某块代码同步执行的。
Future的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用。
这里可以使用then来将特定的需要等待的任务放进去,然后不堵塞后面的任务的执行。 如果then没有返回值的话,那么value就是null。
这里看到Future里面return的话那么value就是return的那个值。这个时候,Future里面返回的数据会被Future包装,然后给到了then里面 的value。
异步中的错误是用catchError来进行处理的。当异步任务中出现异常之后,
如果调用下列方法就会报错,这是因为任务里面抛出了异常没有处理。
getData() async {
print('开始data = $_data');
//耗时操作
Future future = Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
return "假的网络数据";
});
future.then((value) {
print('value = $value');
});
print('做一点其他事情');
}
在.then 下面添加代码
future.catchError((e){print("捕获到了错误:$e");});
运行后发现还是报错,但是捕获到了异常。正常来说如果处理了异常,就不应该报错了,那么这里是执行顺序的问题吗?
将catch和then交换位置后运行,发现还是报错
把then注释掉后发现不报错了。
那么这里要怎么处理呢?这里一般用链式调用。
getData() async {
print('开始data = $_data');
//耗时操作
Future future = Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
return "假的网络数据";
}).then((value) {
print('value = $value');
}).catchError((e) {
print("捕获到了错误:$e");
});
print('做一点其他事情');
}
future.then((value) {
print('value = $value');
},onError: (error){print("捕获到了错误:$error");});
onError是在.then这一次对错误进行处理,而catchError则是多次链式调用中的错误处理
如果在.then之前catchError了,那么.then中的value就是catchError中传过来的值了。在catchError之前的.then不会执行。
在CatchError之后添加了whenComplete运行后发现还是报出了异常。
getData() async {
print('开始data = $_data');
//耗时操作
Future future = Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
return "假的网络数据";
});
future.catchError((e) {
print('error = $e');
return "错误";
}).then((value) => print('$value'));
future.whenComplete(() => print("完成了"));
print('做一点其他事情');
}
这个时候可以在whenComplete之后添加一个catchError,或者使用链式调用。
future.catchError((e) {
print('error = $e');
return "错误";
}).then((value) => print('$value')).whenComplete(() => print("完成了"));
上面的例子可以看到链式调用能避免大部分的错误,所以在一般都是使用链式调用来进行处理的,并且把catchError放到最后调用,这样出现异常的时候,前面的.then就不会执行了。如果链式太长的话,可以创建方法来让链式更加简洁。
getData() async {
print('开始data = $_data');
//耗时操作
Future future = Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
return "假的网络数据";
}).then(thenFunc).whenComplete(completeFunc).catchError((e) {
print('error = $e');
return "错误";
});
print('做一点其他事情');
}
FutureOr thenFunc(String value) {
}
FutureOr completeFunc() {
}
Future是放在队列中的,所以Future的执行是有顺序的。下面的代码结果输出是什么呢?
void main() {
testFuture();
print("A");
}
void testFuture() {
Future((){
sleep(Duration(seconds: 2));
print("C");
});
print("B");
}
运行后看到是B->A->C;
那么下面的代码运行后会打印什么呢?
void main() {
testFuture();
print("A");
}
void testFuture() async {
await Future((){
sleep(Duration(seconds: 2));
print("C");
}).then((value) => print("D"));
print("B");
}
运行后发现是A->C->D->B,这里因为B被C堵塞了,所以A会先执行,然后C执行完之后执行D,最后执行B。
那么下面代码的输出结果是什么呢?
void main() {
testFuture();
print("A");
}
void testFuture() async {
Future((){
return "任务1";
}).then((value) => print("$value结束"));
Future((){
return "任务2";
}).then((value) => print("$value结束"));
Future((){
return "任务3";
}).then((value) => print("$value结束"));
Future((){
return "任务4";
}).then((value) => print("$value结束"));
print("任务添加完毕");
}
运行后发现是任务添加完毕->A->任务1结束->任务2结束->任务3结束->任务4结束。
那么这里任务一定是按顺序执行的吗?在任务二添加sleep后重新执行,发现任务顺序还是一样的。
这说明这里会按异步任务的添加顺序执行的。
当需要等待多个Future完成,并收集它们的结果,可以使用Future.wait。这个时候.then就会等wait里面所有任务完成后在执行,然后返回的值可以用数组来取。wait里面的任务同时处理,但是是按添加顺序执行的,而如果是链式执行的话,则是一个执行完在执行下一个。
Future.wait([
Future((){
return "任务1";
}),
Future((){
return "任务2";
}),
Future((){
return "任务3";
}),
Future((){
return "任务4";
}),
]).then((value) => print(value[0] + value[1] + value[2] ));
在Dart中,实际上有两种队列:
因为microtask queue 的优先级高于 event queue
,所以如果 microtask queue有太多的微任务, 那么就可能会霸占住当前的event loop。从而对event queue中的触摸、绘制等外部事件造成阻塞卡顿。
在每一次事件循环中,Dart总是先去第一个microtask queue中查询是否有可执行的任务,如果没有,才会处理后续的event queue的流程。
异步任务我们用的最多的还是优先级更低的 event queue。Dart为 event queue 的任务建立提供了一层封装,就是我们在Dart中经常用到的Future。
正常情况下,一个 Future 异步任务的执行是相对简单的:
声明一个 Future 时,Dart 会将异步任务的函数执行体放入event queue,然后立即返回,后续的代码继续同步执行。
当同步执行的代码执行完毕后,event queue会按照加入event queue的顺序(即声明顺序),依次取出事件,最后同步执行 Future 的函数体及后续的操作。
上面说过了microtask队列的优先级比较高,那么使用microtask就可以让任务优先执行。
下面代码的先执行代码1,代码2,A以及B。
void testFuture3() {
print('外部代码1');
Future(()=>print('A')).then((value) => print('A结束'));
Future(()=>print('B')).then((value) => print('B结束'));
print('外部代码2');
}
添加了微代码后,那么微任务就会在异步任务之前执行。
void testFuture3() {
print('外部代码1');
Future(()=>print('A')).then((value) => print('A结束'));
Future(()=>print('B')).then((value) => print('B结束'));
scheduleMicrotask(() {
print("微任务A");
});
print('外部代码2');
}
下面的任务执行后打印情况是什么样的呢?这里5一定是先执行的,然后执行微任务3,然后异步任务按照添加的顺序执行,那么就会先执行future1打印1和4,最后执行future2 打印2,所以打印 5 —— 3 —— 1 —— 4 —— 2;
void testFuture4() {
Future future1 = Future((){print('1');});
Future future2 = Future((){print('2');});
scheduleMicrotask(() {
print("微任务3");
});
future1.then((value) => print('4'));
print('5');
}
运行后验证果真是的。
那么这里的打印顺序是什么呢?这里future3最先被添加到队列,所以依然会比 1, 2 优先执行,所以会先打印6,所以打印 5 —— 3 —— 6 —— 1 —— 4 —— 2;
打印结果:
那么如果是这样的话,打印结果会是什么呢?按照图片里的,执行future3的时候会把自身任务执行完再重新开始循环,那么也就是说,这里5,3之后会先打印6,8 ,然后再打印7,然后再打印142。
void testFuture4() {
Future future3 = Future(() => null);
future3.then((value) {
print('6');
scheduleMicrotask(() {
print("7");
});
}).then((value) => print("8"));
Future future1 = Future(() {
print('1');
});
future1.then((value) => print('4'));
Future future2 = Future(() {
print('2');
});
scheduleMicrotask(() {
print("3");
});
print('5');
}
打印结果:
那么把.then拆出来的话是什么结果呢?其实这里相当于把.then里面的任务放到微任务里面去了,所以8依然会优先执行。这也是为什么 future1里面的then的任务会比 future2先执行。
Future future4 = future3.then((value) {
print('6');
scheduleMicrotask(() {
print("7");
});
});
future4.then((value) => print("8"));
Flutter默认是单线程任务处理的,如果不开启新的线程,任务默认在主线程中处理。和iOS应用很像,在Dart的线程中也存在事件循环和消息队列的概念,但在Dart中线程叫做isolate。应用程序启动后,开始执行main函数并运行main。isolate。
下面的打印结果一定是123,因为这里Future会等待主线程睡眠结束。
void IsolateDemo() {
print('1');
Future(func);
sleep(Duration(seconds: 2));
print('2');
}
FutureOr func() {
print('3');
}
那么如果将func放在Isolate里面执行,那么在打印2之前func就会先打印3。
void IsolateDemo() {
print('1');
Isolate.spawn(func,10);
sleep(Duration(seconds: 1));
print('2');
}
void func(int message) {
print('3');
}
void IsolateDemo() {
print('外部代码1');
Isolate.spawn(func,10);
Isolate.spawn(func2,10);
Isolate.spawn(func,10);
Isolate.spawn(func2,10);
Isolate.spawn(func,10);
Isolate.spawn(func2,10);
sleep(Duration(seconds: 1));
print('外部代码2');
}
运行后发现这里任务一不一定比任务2先执行,而且外部代码也会在任务一任务二之前执行,证明了这里Isolate确实是子线程里面执行任务的。
Dart中的isolate更像是一个进程,因为isolate有独立的内存空间,意味着每个isolate中的数据是独立的,所以没有资源抢夺的问题,也就不需要锁。所以,我们访问数据不能直接访问。isolate相当于轻量级的进程,他不会独立开辟堆和栈,而是给了一个局部的内存空间,所有的变量,内存对象都在这个空间里面,和原来的程序传递数据的时候就需要用到进程间的通讯。
声明一个属性没然后在func里面赋值,在IsolateDemo里面睡眠之后打印a的值看一下是否改变。
int a = 10;
void IsolateDemo() {
print('外部代码1');
Isolate.spawn(func,1000);
sleep(Duration(seconds: 1));
print('外部代码来了a=$a');
print('外部代码2');
}
void func(int message) {
a = message;
print('第一个来了a=$a');
}
运行后发现是没有改变的
那么如果非要改变a的值的话,那么就需要用到port,将port的sendPort作为参数传给func,然后添加listen监听数据变化来接受func里面传过来的值。
void IsolateDemo() {
print('外部代码1');
// 创建一个port
ReceivePort port = ReceivePort();
// 创建一个Isolate
Isolate.spawn(func,port.sendPort);
//监听数据变化
port.listen((message) {
a = message;
print('接受到了a=$a');
});
sleep(Duration(seconds: 1));
print('外部代码来了a=$a');
print('外部代码2');
}
void func(SendPort send) {
send.send(100);
print('第一个来了a=$a');
}
运行后发现a的值改变了。
既然我们Isolate开辟了空间,那么我们就要手动去销毁Isolate。 这里创建临时变量iso,然后在port.listen里面关闭端口以及杀掉iso。这里的await不会堵塞后面代码的执行,因为这里是其他线程里面的。
void IsolateDemo() async {
print('外部代码1');
// 创建一个port
ReceivePort port = ReceivePort();
// 创建一个Isolate
Isolate iso = await Isolate.spawn(func,port.sendPort);
//监听数据变化
port.listen((message) {
a = message;
print('接受到了a=$a');
port.close();
iso.kill();
});
print('外部代码来了a=$a');
print('外部代码2');
}
关于Iso还有一个封装叫compute,这里使用compute之后发现这里并不会堵塞住compute的执行。
void computeTest() {
print('外部代码1');
compute(func1,10);
sleep(Duration(seconds: 2));
print('外部代码2');
}
FutureOr func1(message) {
print('compute');
}
运行结果:
compute和iso不一样的是,如果加了await,那么后面的代码就需要等待。并且compute可以返回数据来修改数据的值,但是如果在func1修改a的值的话同样是没有效果的。
void computeTest() async{
print('外部代码1');
a = await compute(func1,10);
print('外部代码2 a = $a');
}
int func1(message) {
sleep(Duration(seconds: 2));
print('compute');
return 1000;
}
下面的打印是同步还是异步呢?
void isoLoadDemo() {
Future( () => compute(testfunc,123)).then((value) => {print('1结束')});
Future( () => compute(testfunc,123)).then((value) => {print('2结束')});
Future( () => compute(testfunc,123)).then((value) => {print('3结束')});
Future( () => compute(testfunc,123)).then((value) => {print('4结束')});
Future( () => compute(testfunc,123)).then((value) => {print('5结束')});
}
void isoLoadDemo() {
Future(() {
compute(testfunc, 123);
}).then((value) => {print('1结束')});
Future(() {
compute(testfunc, 123);
}).then((value) => {print('2结束')});
Future(() {
compute(testfunc, 123);
}).then((value) => {print('3结束')});
Future(() {
compute(testfunc, 123);
}).then((value) => {print('4结束')});
Future(() {
compute(testfunc, 123);
}).then((value) => {print('5结束')});
}
运行后发现这里是同步的了。
这是为什么呢?因为箭头函数其实是return compute(testfunc, 123) 的,如果在下面的代码添加return之后,那么就是异步的了。如果返回的是子线程的Future,那么.then处理的是子线程的Future的结果,否则就是当前Future的结果。
那么 compute(testfunc, 123) 的执行是有序的还是无序的呢?在Future里面添加打印
void isoLoadDemo() {
Future(() {
print('1开始');
return compute(testfunc, 123);
}).then((value) => {print('1结束')});
Future(() {
print('2开始');
return compute(testfunc, 123);
}).then((value) => {print('2结束')});
Future(() {
print('3开始');
return compute(testfunc, 123);
}).then((value) => {print('3结束')});
Future(() {
print('4开始');
return compute(testfunc, 123);
}).then((value) => {print('4结束')});
Future(() {
print('5开始');
return compute(testfunc, 123);
}).then((value) => {print('5结束')});
}
运行后发现开始是按顺序打印的,那么就说明compute(testfunc, 123)是按顺序执行的,但是返回的时机是无序的。
那么同理可知,如果去掉return, 那么就会按顺序打印1开始,1结束 … ,运行后证明是正确的。
其实.then任务和Future可以当作是一个整体。下面代码中,按道理应该是Future里面的微任务1先被添加到队列中,然而实际上却是微任务先执行,所以说这里可以把.then和Future当作是一个整体。
Future x = Future((){
print('异步任务1');
scheduleMicrotask(() {
print('微任务1');
});
});
x.then((value) {
print('微任务2');
});
这里whenComplete也是一样的,并且如果whenComplete在then前面,那么就先于.then执行,否则就后于.then执行。
Timer会默认开启一个异步任务。
void main() {
Timer.run(() {
print('异步任务');
});
print('来了');
}
运行后
回到之前的聊天界面,试一下timer是否会卡住ui。
在initState里面添加一个timer
int _count = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
_count ++;
print('$_count');
if (_count == 99) {
timer.cancel();
}
});
然后在timer启动后拖动ListView,发现timer是没有像ios里面一样是暂停的。但是这里有一个小问题,之前聊天界面是保存状态的,如果没有保存状态的话,那么重新进入聊天页面,就会重新走一个init,重新创建一个timer,而前面的timer没有被取消,那么就会有多个tiemr存在的情况。
这个时候就需要用到dispose,当State 被永久的从视图树中移除时,Flutter 会调用dispose 函数。
添加一个变量timer,然后给timer赋值。
late Timer _timer;
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
_count ++;
print('$_count');
if (_count == 99) {
timer.cancel();
}
});
在dispose中判断如果_timer有值且正在运行,那么就将其取消掉。
@override
void dispose() {
// TODO: implement dispose
if (_timer != null && _timer.isActive) {
_timer.cancel()
}
super.dispose();
}
什么时候该使用多线程,什么时候该使用异步呢?
在聊天界面的AppBar的actions添加一个GestureDetector,里面是一个添加按钮,然后在onTap里面添加一个异步的耗时操作。
GestureDetector(
child: Container(
child: Icon(Icons.add),
),
onTap: () {
Future(() {
print('开始');
for (int i = 0; i < 1000000000; i++) {
}
print("结束了");
});
},
),
点击后发现主线程完全被卡死了,页面滑动不了,并且timer也不动了。这个时候就需要将耗时操作放在子线程执行,这样主线程就不会被卡住了。
onTap: () {
Future(() {
return compute(func,123);
});
},
FutureOr func(int message) {
print('开始');
for (int i = 0; i < 1000000000; i++) {
}
print("结束了");
}
总结: