Farlanki.

在 Flutter 中使用多 Isolate

字数统计: 1k阅读时长: 4 min
2019/02/18 Share

我们在编程的时候经常会用到多线程。多线程技术能让我们得以利用多核CPU的优势,能将一些繁重的任务交给别的线程运算。在 iOS 编程中,我们需要小心对待在主线程执行的耗时长的方法,因为一旦我们阻塞了主线程,应用的 UI 就会被卡住,这是一种十分不好的用户体验。在使用 flutter 的时候,其实也会有类似问题。那么,我们在使用 Flutter 的时候,又应该如何使用“多线程”呢?

isolate

dart 语言中的’多线程‘被称为 isolate。事实上,isolate 是一种不共享状态的多线程技术。dart语言设计 isolate 这种体系能避免线程竞争等情况的出现。也是因为 isolate 之间是不能共享状态的,所以 isolate 中只能调用静态方法或者顶层方法。主 isolate 和其他 isolate 通过接口进行通信。这样,各个 isolate 中的状态就是独立的。

例子

设想我们在完成这样一个方法:从应用本地沙盒中读取一个长度为数万单词列表,返回一个 List。由于单词列表的长度十分长,并且是读文件操作,所以我们必须将这个方法放在另一个 isolate 执行,否则一旦阻塞了主 isolate ,UI 将会被卡住。

首先,我们需要创建两个方法,一个是暴露给外部的方法,另一个是处理的单词的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyWordReader

Future<String> _getFilePath() async {
// async function here
}


Future<List<CYWord>> getRawWordList() async {
//取得文件路径
String filePath = await _getFilePath();
//to be done
}

static Future<void> _getRawWordList(SendPort sendPort) async {
//to be done
}

留意getRawWordList,我们首先需要取得文件路径。然后再看看_getRawWordList,这个内部方法是静态的,并且需要一个 sendPort 作为参数。这个 sendPort 是什么,将会在下面说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class MyWordReader
{
Future<String> _getFilePath() async {
// async function here
}

Future<List<CYWord>> getRawWordList() async {
//取得文件路径
String filePath = await _getFilePath();

//创建一个 ReceivePort
ReceivePort receivePort = ReceivePort();

//创建 isolate , 并且将和 ReceivePort 对应的 SendPort 传给 isolate
await Isolate.spawn(_getRawWordList, receivePort.sendPort);
List<String> words;
await for (var value in receivePort) {
if (value is SendPort) {
//接受到另一个 isolate 的SendPort,使用该 port 发送文件路径
SendPort sendPort = value;
sendPort.send(filePath);
} else if (value is List) {
//接收到结果,结束
words = value;
receivePort.close();
break;
}
}
return words;
}
}

static Future<void> _getRawWordList(SendPort sendPort) async {
ReceivePort port = ReceivePort();
sendPort.send(port.sendPort);
//等待文件路径
String filePath = await port.first;
port.close();

List<String> result = new List<String>();
var fields = await input
.transform(utf8.decoder)
.transform(csvCodec.decoder)
.toList();
for (List<dynamic> list in fields) {
if (list.length > 0) {
result.add(list[0]);
}
}
//返回结果
sendPort.send(result);
}
}

以上就是完整的代码。这里解释一下 ReceivePort 和 SendPort。ReceivePort 和 SendPort 是一一对应的。在上述程序中,有两个 isolate,每个 isolate 都有自己的 ReceivePort 和接受到的另一个 isolate 的 SendPort。

另外,工作 isolate 向主 isolate 发送了两次消息。第一次是将自己的 SendPort 发送给主线程,让主线程得以将文件路径传给自己。第二次是将结果返回给主 isolate。主 isolate 使用await for 方法等待所有消息,针对不同的消息做出不同的处理。当接受到结果的时候,关闭port,返回结果。

compute function

compute function 是 flutter 基于 isolate 的一个封装。但是,这个issue说明,compute function 是不支持传入一个异步的方法的。所以这里仅仅展示其用法,没办法将之前的例子改为使用 compute function 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int getnthPrime(int n) {
int currentPrimeCount = 0;
int candidate = 1;
while(currentPrimeCount < n) {
++candidate;
if (isPrime(candidate)) {
++currentPrimeCount;
}
}
return candidate;
}
void _getPrime() async {
int ans = await compute(getnthPrime, 10000);
setState((){
prime=ans;
});
}

上面的代码展示了如何使用 compute function 计算第 n 个质数。

可见,isolate 接口比 compute functinon 功能更加强大。

参考文章:

1.Flutter async : Beginner friendly guide for heavy lifting operations
2.The Event Loop and Dart

CATALOG
  1. 1. isolate
    1. 1.1. 例子
  2. 2. compute function
  3. 3. 参考文章: