我们在编程的时候经常会用到多线程。多线程技术能让我们得以利用多核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 { }
Future<List<CYWord>> getRawWordList() async { String filePath = await _getFilePath(); }
static Future<void> _getRawWordList(SendPort sendPort) async {
}
|
留意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 { }
Future<List<CYWord>> getRawWordList() async { String filePath = await _getFilePath(); ReceivePort receivePort = ReceivePort(); await Isolate.spawn(_getRawWordList, receivePort.sendPort); List<String> words; await for (var value in receivePort) { if (value is SendPort) { 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