Kalec的网络日志

未来连结

HOME 首页 Flutter Isolate应用

Flutter Isolate应用

技术
2019-04-18 15:30:23

Flutter Isolate应用

为啥用 isolate

dart 的执行机制和 js 非常类似,同样是异步单线程,类似的事件循环。在大部分时候十分高效。但在大量数据处理的时候,或者 cpu 密集型运算的时候,这种时候,就没那么高效了,特别是在前端在这种时候 ui 卡顿的时候就尤为明显,用户体验极其糟糕。

最近用 flutter 开发的阅读器上就遇到了类似的问题,由于存在有笔迹的功能,并且需要在各种设备上笔迹显示相同,还有些书签,检索,阅读进度等功能,这就需要先对 txt 文本进行处理,并且把每页信息存放到 SQLite 中,来保证阅读的顺畅。

读文本的方式如下:

Stream lines = stream.transform(utf8.decoder).transform(LineSplitter()); await for (String first in lines) { first += '\n'; cur += first; if (cur.length >= LARGEST_NUMBER_WORDS /* 单页最多字数 */ ) { print('字数 ==> ${cur.length}'); int pageTextNum = MyPainter.rangStr(cur, maxLen: cur.length); // <--这里面计算当前页填充的字数(由于存在换行,每页字数会不相同) curText = cur.substring(0, pageTextNum); insertDB(IndexDBBase(page: index + 1, startIndex: totalNumberWords, content: curText)); totalNumberWords += pageTextNum; cur = cur.substring(pageTextNum); print('剩余字数 ${cur.length} ==>> $cur'); index++; // 页码 } } print(cur); int totalWords = totalNumberWords + cur.length; insertDB(IndexDBBase(page: index + 1, startIndex: totalNumberWords, content: cur)); insertBookDB(total_size: totalWords, total_page_num: index + 1); await db.commitBatch();

在测试的时候读取大文本,运行在 Profile 模式下,可以很直观的看到主 isolate 已经爆炸了,ui 已经开始异常的卡顿,如图表中所见,一帧需要大概 60ms 左右,换算过来 1000 / 60 也就是大概仅仅只能达到 16fps,显然这种卡顿我是无法接受的。
单isolate运作.20190418 15_19_36.gif

这种时候就凸显出多线程的重要性了。

isolate 说明

dart isolate 有点类似于 webworkers,都是有着独自的内存来确保线程安全,(当然 webworker 可以通过 atomics 来进行内存共享,这里暂时不提)。

粗略看了下文档,大概就是需要有个接受端口,和发送端口,图省事吧,看了下别人的用法。总结的还不错:

  1. Send Port — Sends message to isolate.

  2. Receive Port — Receive message from isolate.

We want to setup our code keeping these points in mind.

  1. There will be 2 isolates. Lets call one main flutter isolate, and other prime isolate.

  2. Main isolate must have both send and receive port of prime isolate.

  3. Send port of prime isolate will be used to send the parameter n.

  4. Receive port of prime isolate will be used to get resultant prime number.

  5. Prime isolate should have both send and receive port of main isolate.

  6. Receive port of main isolate will be used to receive the parameter n.

  7. Send port of main isolate will be used to send the resultant prime number.

首先得用Isolate.spawn(..., ...)创建子isolate,当然由于不共享内存,必须是纯静态的回调来运作在 prime isolate上。

使用方式就是在 main isolate 上创建一个 ReceivePort 用来接收 prime isolate 创建的 ReceivePort,main 的 isolate 上得到 prime 上的 port 后即可对 prime 发送需要处理的内容以及新的 ReceivePort,每当处理完成的时候通过这个 port 返回回来。

说的比较乱,再来捋一捋,也就是说,两个 isolate 通讯都需要有各自的接收端口,并且都需要传给对方。这里用同一个端口进行传输的话会存在两种或者更多种数据(至少就是传给对方端口,还有需要处理的数据),由于 dart 和 JavaScript 一样不支持函数重载,直接判断传输的类型来识别那个是传递过来的端口,那个是需要处理数据这么处理又感觉不是很优雅。就如上所示的,初始的端口只用来让相互双方传递各种端口,然后双方得到各种应该传输的‘’门牌号‘’后,在重新创建个传递数据的应答端口,每次数据传递给prime isolate的时候携带上,prime isolate处理完成后通过应答口传回。最后不需要prime isolate的时候记得kill掉。

可能还是不够清楚,那就看图吧!
未命名.png

大致看了下文档。在不进行异步的情况,直接用 compute 即可,实际上,这个也就是 Isolate 的封装而已。详细看文档

他这里封装的 compute 封装的很棒,直接用了一个类通过实现 call,把类作为参数来传递了一个回调。通过_spawn在prime Isolate中启用时候,再来调用这个call,实现一个精巧的多线程运算。

在compute中不能进行异步的原因是因为

  • isolate中不允许共享内存,造成无法传递Future。

  • _spawn回调中,它只封装了同步回调,若进行异步,则会返回Future。

文本解析Isolate设计

在做txt文本解析的Isolate的时候,考虑到这个解析仅仅只是使用一次,存完库后就再也不使用了。对于这种情况用最初别人的那个用法是不太优雅的,并不需要创建多个端口来做持续的处理。用compute的方式确实符合这种设计,但是读取文本行的流的时候,是异步的。

这个时候只有照着compute的方式来做处理,把_spawn 改成异步方式最为恰当。

改完后发现一点小问题,就是在Isolate中进行sqlite存储的时候出现了异常。去官方issue : https://github.com/flutter/flutter/issues/13937

发现,在prime Isolate中调用平台能力确实有这个问题的存在。这时候只有牺牲一些性能把sqlite的操作移动到main Isolate中来完成。

最终在文本解析的prime Isolate设计就如下:

Future getPrime({String keyName, String filePath}) async { ReceivePort receivePort = ReceivePort(); Isolate isolate = await Isolate.spawn(initDoc, CrossIsolateMessage(keyName: keyName, filePath: filePath, sendPort: receivePort.sendPort)); final result = await receivePort.first as ComputedBook; receivePort.close(); isolate?.kill(); print(result); return result; } static initDoc(CrossIsolateMessage msg) async { File file = await _getLocalFile(msg.filePath); ComputedBook computedBook = await _getTextIndex(file); msg.sendPort.send(computedBook); } static Future _getTextIndex(File file) {...};

可见这里ui流畅的一笔。
分配子isolate后.20190418 15_22_57.gif

最后说两句

总之,这次阅读器的设计收获还是颇多的。特别难点在分辨率适配,画布翻页处理,还有画布的性能优化等等。有机会我还是会拿出部分来单独写一下,做个分享。关于开源,暂时不会开源!咕咕咕!

留言

  • 暂时没有留言,来留下你的留言吧!

评论

看不清?换一个