这是我参与11月更文挑战的第21天,活动详情查看:2021最后一次更文挑战
1. 多线程
1.1. isolate
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');
}
1.2 compute
关于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;
}
2. 异步和多线程的结合
下面的打印是同步还是异步呢?
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执行。
4. Timer
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();
}
5. 多线程和异步使用时机
什么时候该使用多线程,什么时候该使用异步呢? 在聊天界面的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("结束了");
}
2. 总结:
- Dart中的多线程
- Dart是单线程语言,但并不代表它不能并行执行代码。因为它拥有Isolate。
- Isolate
- Isolate可以看成是一个小的进程。
- 它拥有独立的内存空间,不同Isolate之间通过消息进行通信
- 它拥有自己的事件循环及队列(MicroTask 和 Event)
- Isolate使用
- 1、创建子线程任务:Isolate.spawn(arg1,arg2);
- arg1: 需要在子线程执行的函数
- arg2:传递给函数的参数
- 这样就在另一个线程中执行arg1的代码了。
- 2、端口通讯
- ReceivePort port = ReceivePort()//构造一个端口。
- port.listen(arg)//注册一个回调监听
- arg为回调函数。参数为消息内容
- 在子线程中.通过port.send() 发送消息
- 3、关闭端口,销毁Isolate
- 注意端口使用完毕需要调用port.close()函数关闭
- Isolate使用完毕,需要调用Isolate.kill()函数销毁
- 1、创建子线程任务:Isolate.spawn(arg1,arg2);
- Isolate可以看成是一个小的进程。
- compute
- 由于dart中的Isolate比较复杂,数据传输比较麻烦,因此flutter在foundation库中封装了一个轻量级compute操作
- 使用:compute(func,count)
- func:子线程函数!func如果有返回值会直接被compute函数返回出去!
- count: 给函数的参数
- 异步多线程结合
- Dart中的异步是可以和多线程结合使用的。
- 如果Future中返回子线程的返回值。那么Future的处理是异步的
- 如果Future中没有返回子线程的返回值。那么Future的处理是同步的
- Future的结果处理会在Future执行完毕立即执行。可以看做是一个任务。