Future的错误和状态
让异步耗时操作处理完成之后,再执行下面代码,Future
前面需要添加await
String _data = '0';
void main() {
getData();
print('做其他事情');
}
getData() async {
print('开始data=$_data');
//耗时操作
//1.后面的操作必须是异步才能使用await修饰
//2.当前这个函数也必须是async修饰的函数!
await Future(() {
for (int i = 0; i < 1000000000; i++) {}
return '网络数据';
}).then((value) => print('value:$value'));//使用Future,相当于把里面的代码块,丢进异步任务里面去!!
// async与await配合使用,主要是让上面异步任务执行完成再执行这里,Future前面需要添加await
print('再多干点事情!!');
}
下面我们来处理网络请求过程中,可能出现的异常状态
- 网络请求
抛出异常状态
String _data = '0';
void main() {
getData();
print('做其他事情');
}
getData() async {
print('开始data=$_data');
//耗时操作
//1.后面的操作必须是异步才能使用await修饰
//2.当前这个函数也必须是async修饰的函数!
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {}
throw Exception('网络异常');
});
future.then((value) => print('value:$value'));//使用Future,相当于把里面的代码块,丢进异步任务里面去!!
// 上面异步任务执行完成再执行这里,Future前面需要添加await
// async与await配合使用,主要对下面代码执行有效
print('再多干点事情!!');
}
- 网络请求
捕获异常
String _data = '0';
void main() {
getData();
print('做其他事情');
}
getData() async {
print('开始data=$_data');
//耗时操作
//1.后面的操作必须是异步才能使用await修饰
//2.当前这个函数也必须是async修饰的函数!
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {}
throw Exception('网络异常');
});
future.then((value) => print('value:$value'));//使用Future,相当于把里面的代码块,丢进异步任务里面去!!
future.catchError((e) => print('捕获到了:'+e.toString()));
// 上面异步任务执行完成再执行这里,Future前面需要添加await
// async与await配合使用,主要对下面代码执行有效
print('再多干点事情!!');
}
通过日志可以看出,我们已经捕获到了异常,程序依然发生Exception
错误,该怎么解决呢?
- 方案一:我们需要使用
链式写法
,来避免捕获异常程序报错的情况。
future.then((value) => print('value:$value'))
.catchError((e) => print('捕获到了:'+e.toString()));
- 方案二:使用
.then
里面的onError
future.then((value) => print('value:$value'),
onError: (e) => print(e.toString()));
onError
与catchError
的区别?
onError
只在当前这一次.then
回调里面有效,而catchError
是对整个future
链式内有效。
现在把. catchError
与. then
顺序交换一下
future.catchError((e) => print('捕获到了:'+e.toString()))
.then((value) => print('value:$value'));
. catchError
已经捕获了异常,.then
的回调依然执行了。
- 网络请求
完成状态
future.then((value) => print('value:$value'))
.catchError((e) => print('使用的时候捕获到了:'+e.toString()));
future.whenComplete(() => print('完成了'))
.catchError((e) => print('完成的时候捕获到了:'+e.toString()));
.then
使用的时候,. whenComplete
完成的时候都可以捕获异常
。
使用简便的写法,注意.catchError
要写在最后面。
- 写法一
getData() async {
print('开始data=$_data');
//耗时操作
//1.后面的操作必须是异步才能使用await修饰
//2.当前这个函数也必须是async修饰的函数!
Future(() {
for (int i = 0; i < 1000000000; i++) {}
throw Exception('网络异常');
}).then((value) => print('value:$value'))
.whenComplete(() => print('完成了'))
.catchError((e) => print('捕获到了:'+e.toString()));
print('再多干点事情!!');
}
- 写法二,这种写法
. catchError
写在前面并不会报错,但是最好把. catchError
放在后面,防止程序出现一些不必要的报错。
getData() async {
print('开始data=$_data');
//耗时操作
//1.后面的操作必须是异步才能使用await修饰
//2.当前这个函数也必须是async修饰的函数!
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {}
throw Exception('网络异常');
});
future.catchError((e) => print('捕获到了:'+e.toString()))
.then((value) => print('value:$value'))
.whenComplete(() => print('完成了'));
print('再多干点事情!!');
}
注意:网络请求最好使用链式写法
,如果网络请求回调里面代码量大的话,完全可以把回调里面的处理抽成方法
。
多个异步处理
多线程与异步的区别:多线程的执行顺序是随机的,异步的执行是有序的
。
如果一个方法里面有多个Future
异步任务,那么执行顺序是什么样呢?
- 案例一
void main() {
testFuture();
print('A');
}
void testFuture() async {
Future(() {
return '任务一';
}).then((value) => print('$value结束'));
Future(() {
return '任务二';
}).then((value) => print('$value结束'));
Future(() {
return '任务三';
}).then((value) => print('$value结束'));
Future(() {
return '任务四';
}).then((value) => print('$value结束'));
print('任务添加完毕');
}
- 案例二:任务二休眠一秒钟,查看执行顺序
import 'dart:io';
void main() {
testFuture();
print('A');
}
void testFuture() async {
Future(() {
return '任务一';
}).then((value) => print('$value结束'));
Future(() {
sleep(Duration(seconds: 1));
return '任务二';
}).then((value) => print('$value结束'));
Future(() {
return '任务三';
}).then((value) => print('$value结束'));
Future(() {
return '任务四';
}).then((value) => print('$value结束'));
print('任务添加完毕');
}
由上面两个案例可以看出,任务队列的执行是有序的
,根据异步代码的添加顺序来执行。
如果任务的执行有依赖关系,该怎么解决?
- 案例三:
任务二依赖于任务一的结果
等等,使用.then
嵌套处理
import 'dart:io';
void main() {
testFuture();
print('A');
}
void testFuture() {
Future(() {
sleep(Duration(seconds: 1));
return '任务一';
}).then((value) {
print('$value结束');
return '$value任务二';
}).then((value) {
print('$value结束');
return '$value任务三';
}).then((value) {
print('$value结束');
return '$value任务四';
});
print('任务添加完毕');
}
如果有多个任务,都要执行结束再,该怎么解决?
- 案例四
import 'dart:io';
void main() {
testFuture();
print('A');
}
void testFuture() {
Future.wait([
Future(() {
return '任务一';
}),
Future(() {
return '任务二';
}),
Future(() {
return '任务三';
}),
]).then((value) => print(value[0] + value[1] + value[2]));
}
- 案例五:任务二休眠一秒钟,查看执行顺序
import 'dart:io';
void main() {
testFuture();
print('A');
}
void testFuture() {
Future.wait([
Future(() {
print('任务一 执行');
return '任务一';
}),
Future(() {
sleep(Duration(seconds: 1));
print('任务二 执行');
return '任务二';
}),
Future(() {
print('任务三 执行');
return '任务三';
}),
]).then((value) => print(value[1] + value[0] + value[2]));
}
里面的三个任务依然是一个串行队列
。
Dart事件循环
上面我们在执行任务的时候都是串行队列
,现在有一个比较紧急的任务想提前执行,该怎么解决呢?
- 案例一:使用
scheduleMicrotask
微任务,优先级更高;微任务的优先级比事件队列
高。
import 'dart:async';
import 'dart:io';
void main() {
testFuture();
print('A');
}
void testFuture() {
print('外部代码一');
Future(() => print('A')).then((value) => print('A结束'));
Future(() => print('B')).then((value) => print('B结束'));
scheduleMicrotask(() {
print('微任务A');
});
sleep(Duration(seconds: 2));
print('外部代码二');
}
在Dart中,实际上有两种队列
:
-
事件队列(event queue)
,包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。 -
微任务队列(microtask queue)
,表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由 Dart内部产生。
因为microtask queue
的优先级高于event queue
,所以如果 microtask queue
有太多的微任务, 那么就可能会霸占住当前的event loop
。从而对event queue
中的触摸、绘制等外部事件造成阻塞卡顿。
推荐学习文章:Dart的事件循环
如果有多个微任务执行,顺序又是什么呢?
- 案例二:
多个微任务执行
void testFuture() {
// x任务开始
Future x = Future(() => print('1'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
// x任务处理
x.then((value) => print('4'));
print('5');
}
首先执行主线程任务打印5
,再执行微任务打印3
,最后执行同步队列打印 1 4 2
。
- 案例三
void testFuture() {
Future x1 = Future(() => null);
Future x = Future(() => print('1'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
x.then((value) => print('4'));
print('5');
x1.then((value) => print('6'));
}
首先执行主线程任务打印5
,再执行微任务打印3
,最后执行同步队列打印 6 1 4 2
。
- 案例四
void testFuture() {
Future x1 = Future(() => null);
x1.then((value) {
print('6');
scheduleMicrotask(() => print('7'));
}).then((value) => print('8'));
Future x = Future(() => print('1'));
x.then((value) => print('4'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
print('5');
}
首先执行主线程任务打印5
,再执行微任务打印3
,再执行同步队列任务,由于x1
任务中.then
回调里面的代码,就是放在微任务里面
,所以先执行8
,再执行7
,同步队列打印顺序是6 8 7 1 4 2
。
- 案例五
void testFuture() {
Future x1 = Future(() => null);
Future x2 = x1.then((value) {
print('6');
scheduleMicrotask(() => print('7'));
});
// .then里面的代码块就是放在微任务里面执行
x2.then((value) => print('8'));
Future x = Future(() => print('1'));
x.then((value) => print('4'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
print('5');
}
- 案例六
void testFuture() {
Future x1 = Future(() => null);
Future x2 = x1.then((value) {
print('6');
scheduleMicrotask(() => print('7'));
});
// .then里面的代码块就是放在微任务里面执行
x2.then((value) => print('8'));
Future x = Future(() => print('1'));
x.then((value) {
print('4');
Future(() => print('9'));
}).then((value) => print('10'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
print('5');
}
x
任务执行到.then
才会添加任务9
,说明任务9
在任务2
之后,所以先打印2
,最终打印结果是5 3 6 8 7 1 4 10 2 9
Dart中的多线程Isolate
上面我们学习的Dart
都是在主线程中操作,也就意味着执行的顺序都是同步执行
的;Future
只是异步执行,并没有开启子线程,依然是在主线程中执行
的。
- 案例一
import 'dart:async';
import 'dart:io';
void main() {
IsolateDemo();
}
void IsolateDemo() {
print('1');
Future(func);
sleep(Duration(seconds: 2));
print('2');
}
func() => print('3');
主线程任务卡住了,任务3
得等待主线程执行结束,才能执行;下面我们来学习Flutter中多线程
的用法,来解决主线程卡住不能执行异步任务
的问题,关键词Isolate
。
import 'dart:async';
import 'dart:io';
// 导入头文件
import 'dart:isolate';
void main() {
IsolateDemo();
}
void IsolateDemo() {
print('1');
Isolate.spawn(func,10);
sleep(Duration(seconds: 2));
print('2');
}
func(int count) => print('3');
成功解决,主线程卡住了,不用等待主线程执行结束再去执行异步任务
。
- 案例二:
Dart
中的多线程
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: 2));
print('外部代码2');
}
func(int count) {
print('第一个来了$count');
}
func2(int count) {
print('第二个来了$count');
}
可以看出任务的执行是随机的
,这就是Dart
中的多线程;Isolate
有独立的内存空间,更像是进程
,每个进程之间的数据是独立的,也就意味着不存在资源抢夺的问题
,也就不需要锁
。Dart
中的多线程不需要锁,这样用起来就非常便捷。
- 案例三:验证
Dart
多线程中数据的独立性
int a = 10;
void IsolateDemo() {
print('外部代码1');
Isolate.spawn(func,100);
sleep(Duration(seconds: 2));
print('a = $a');
print('外部代码2');
}
func(int count) {
a = count;
print('第一个来了: a = $a');
}
子线程中已经把a
的值更改了,主线程中的a = 10
,说明主线程中的数据与子线程中的数据是独立的
;成功验证了数据独立性
。
- 案例四:如果子线程中更改了
a
的值,想让主线程的值也更改,可以通过listen
监听更改。
int a = 10;
void IsolateDemo() {
print('外部代码1');
// 创建一个port
ReceivePort port = ReceivePort();
// 创建Isolate
Isolate.spawn(func, port.sendPort);
// 监听数据变化
port.listen((message) {
a = message;
print('接收到了$a');
});
sleep(Duration(seconds: 2));
print('a = $a');
print('外部代码2');
}
func(SendPort send) {
send.send(100);
print('第一个来了: a = $a');
}
- 案例五:
Isolate
开辟了一个独立的空间,就需要手动去关闭该空间;关键词kill
int a = 10;
void IsolateDemo() async {
print('外部代码1');
// 创建一个port
ReceivePort port = ReceivePort();
// 创建Isolate
// 这里使用了await, 方法后面就需要添加async
Isolate iso = await Isolate.spawn(func, port.sendPort);
// 监听数据变化
port.listen((message) {
a = message;
print('接收到了$a');
// 关闭端口port
port.close();
// 杀掉Isolate子线程
iso.kill();
});
sleep(Duration(seconds: 2));
// Isolate添加await并不会阻塞这里的代码执行
print('a = $a');
print('外部代码2');
}
func(SendPort send) {
sleep(Duration(seconds: 1));
send.send(100);
print('第一个来了: a = $a');
}
Dart
中多线程除了有Isolate
,还有一个是基于Isolate
的封装compute
- 案例六:多线程
compute
的使用
import 'dart:async';
import 'dart:io';
import 'dart:isolate';
// 导入头文件
import 'package:flutter/foundation.dart';
void main() {
computeDemo();
}
void computeDemo() {
print('外部代码1');
compute(func2, 10);
sleep(Duration(seconds: 2));
print('外部代码2');
}
func2(int count) {
print('第二个来了$count');
}
- 案例七:
compute
可以接收方法返回值,同时可以用来更改主线程的变量值
int a = 10;
void computeDemo() async {
print('外部代码1');
a = await compute(func2, 10);
print('外部代码2: a = $a');
}
int func2(int count) {
sleep(Duration(seconds: 2));
print('第二个来了');
return 10000;
}
- 案例八:
compute
方法内部并不能更改主线程变量值
int a = 10;
void computeDemo() async {
print('外部代码1');
int b = await compute(func2, 10);
print('外部代码2: a = $a b = $b');
}
int func2(int count) {
a = 999;
sleep(Duration(seconds: 2));
print('第二个来了');
return 10000;
}
compute
只是对Isolate
做了一层封装,线程间的通信本质还是用到listen
传值监听;compute
不需要手动kill
关闭,但是创建端口port
还是要手动close
。
pubspec.yaml文件管理
前面我们在进行网络请求的时候,使用的是官方http
,还有一个网络请求框架dio
;dio是一个强大的Dart Http
请求库,支持Restful API
、FormData
、拦截器
、请求取消
、Cookie管理
、文件上传/下载
、超时
、自定义适配器
等...
dio网络请求库 - 用法学习
我们在使用http
网络请求的时候,导入头文件import 'package:http/http.dart' as http;
。
/* 关于import
* 1、as关键字 -- 给库起别名! 目的:防止类名方法名冲突!
* 2、导入库,默认是整个文件中的都会导入,下面是两个关键字
* * show:执行需要导入的内容
* * hide:需要隐藏的内容。
* */
pubspec.yaml
文件介绍
# 项目名称,必填字段
name: future_demo
# 项目描述,非必填字段
description: A new Flutter project.
# 指定包发布的位置 none表示不发布,默认发布在 https://pub.dev/
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 当前flutter工程版本
version: 1.0.0+1
# dart sdk指定兼容版本
environment:
sdk: ">=2.12.0 <3.0.0"
# 三方flutter sdk 默认指定最新版本
dependencies:
flutter:
sdk: flutter
# 也可以指定flutter sdk版本
version: ***
# 开发环境的依赖库
cupertino_icons: ^1.0.2
dio: ^4.0.1 # ^表示大版本不变的区间写法。相当于'>=4.0.1 < 5.0.0',引用这个版本区间的最新版本
# dio: 4.0.1 指定4.0.1
# dio: any 任意版本,默认是最新版本
# dio: '>3.0.1' 大于3.0.1(不包含),配置大于小于的话要添加字符串
# 开发打包,开发环境依赖的一些库版本,调试的时候使用
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.0
# The following section is specific to Flutter.
# 项目中配置的一些声明方式,主要涉及flutter项目的资源
flutter:
uses-material-design: true
# 资源image
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# example:
# 配置字体
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
注意: 配置文件中缩进的对齐
。
pubspec.yaml
文件配置完成之后,上面会有提示
Dio
库的使用
// 导入头文件
import 'package:dio/dio.dart';
void main() {
dioDemo();
}
void dioDemo() {
//发送网络请求
final dio = Dio();
dio.get('https://www.baidu.com').then((value) => print(value));
}