Flutter的多线程
Flutter中使用的是Dart语言,而Dart是一种单线程的设计。虽然如此,但不代表Flutter(或者说Dart)就不支持多线程。这篇文章就研究一下如何在Flutter(Dart)中使用多线程。
什么是Isolate
英文单词是‘隔离’的意思,来看看声明中的部分注释:
* An isolated Dart execution context.
*
* All Dart code runs in an isolate, and code can access classes and values
* only from the same isolate. Different isolates can communicate by sending
* values through ports (see [ReceivePort], [SendPort]).
*
* An `Isolate` object is a reference to an isolate, usually different from
* the current isolate.
......
*
* Isolates run code in its own event loop, and each event may run smaller tasks
* in a nested microtask queue.
*
...
简单摘要一下大概意思:
- dart代码执行是在isolate中,在相同的isolate中,代码可以访问定义的类和值。不同的isolate之间,使用port方式进行通讯。
- 当前的isolate与创建出来的isolate通常是不同的。
- ......
- Isolates中运行的代码是在它自己的事件循环中,并且每一个事件可以嵌套运行在微任务队列中。
- ......
1.实现多线程逻辑
通过以上的这些注释,说明使用isolate可以实现一些多线程的相关操作。
示例代码:
void main() {
isolateDemo();
}
void isolateDemo() {
Future(() => Isolate.spawn(func1, 123)).then((value) => print('1结束了'));
Future(() => Isolate.spawn(func1, 123)).then((value) => print('2结束了'));
Future(() => Isolate.spawn(func1, 123)).then((value) => print('3结束了'));
Future(() => Isolate.spawn(func1, 123)).then((value) => print('4结束了'));
Future(() => Isolate.spawn(func1, 123)).then((value) => print('5结束了'));
}
void func1(int count) {}
打印结果:
2. isolate不等价于Thread
在ios开发中,我们使用多线程都是基于NSThread来实现,此时大家应该会感觉到isolate和thread差不多,其实不然。isolate要比thread要更底层一些,更像是个进程。
在声明注释中有这样的描述:
- Different isolates can communicate by sending values through ports (see [ReceivePort], [SendPort]).
- Isolates run code in its own event loop, and each event may run smaller tasks in a nested microtask。
意思是说不同的isolate之间用port通讯,isolate有自己的事件队列和微任务队列。这两点就可以证明‘isolate是进程操作’的猜想。
3. isolate的独立空间
int a = 10;
void main() {
Isolate.spawn(func, 123);
//sleep 1s
sleep(Duration(seconds: 1));
print(Isolate.current.debugName + ' a = $a');
}
void func(int count) {
a = count;
print(Isolate.current.debugName + ' func中a = $a');
}
- 定义一个全局变量a=10
- main函数中,开启一个isolate,调用func函数,传参入参数‘123’。睡一秒后,打印a的值
- func函数中,接受到的形参赋值给a,打印a的值
运行结果:
flutter: func func中a = 123
flutter: main a = 10
无论sleep设置多长时间,两个打印的a值都不相同,这里可以证明a在两个isolate中,有各自的独立内存空间,互不影响。还有一点好处是不用担心多线程的资源抢夺问题。
4. isolate之间的数据交互
通过端口来达到isolate之间的数据交互。
int a = 10;
void main() {
test();
}
void test() async {
//创建port端口
ReceivePort port = ReceivePort();
//创建isolate实例,注意此处需要await修饰,但此await不会影响后面的同步代码执行,
Isolate iso = await Isolate.spawn(func, port.sendPort);
port.listen((message) {
a = message;
print('listen a = $a');
//关闭端口
port.close();
//关闭iso
iso.kill();
});
sleep(Duration(seconds: 1));
print('a = $a');
}
void func(SendPort send) {
send.send(1000);
}
代码中创建isolate实例,语法要求需要await修饰,但此时的await和修饰Future的await的功能不一样,它不会阻碍同步代码的执行。
对修饰Future的await修饰情况不了解的,可以查看本人之前的文章Flutter学习-Dart异步编程中的async和await中的示例。
注意:sleep函数无论设置多长时间,都是先打印,后执行port.listen中的回调。
打印结果:
flutter: a = 10
flutter: listen a = 1000
5. compute
因为isolate比较偏底层,所以,代码写起来比较繁琐。 compute是对isolate封装,使用起来更方便一些。比如compute中的回调方法是有返回值的,而isolate.spawn中的回调方法是没有的。
int a = 10;
void main() {
computeDemo();
}
void computeDemo() async {
print('外部代码1');
a = await compute(func2, a);
print('a = $a');
sleep(Duration(seconds: 1));
print('外部代码2');
}
//有返回值
int func2(int count) {
return 10000;
}
打印结果:
flutter: 外部代码1
flutter: a = 10000
flutter: 外部代码2
通过compute声明中的注释,我们能看到compute中的回调方法的返回值是Future类型,那么我们也可以用链式的方式来写代码,这样就不会阻塞同步代码了:
int a = 10;
void main() {
computeDemo();
}
void computeDemo() {
print('外部代码1');
//链式实现
compute(func2, a).then((value) {
a = value;
print('a = $a');
});
sleep(Duration(seconds: 1));
print('外部代码2');
}
int func2(int count) {
return 10000;
}
打印结果:
flutter: 外部代码1
flutter: 外部代码2
flutter: a = 10000