在Vue中使用多线程——Comlink-loader的应用
Comlink精妙的地方,我个人认为在于将数据传递的操作变成了一个异步的操作,这样我们就能很好的利用ES6所提供的async/await语法糖,将数据的传递与接收逻辑写得非常简洁优雅。开发者不需要再去考虑事件订阅所带来的各种复杂度。
前言
最近的部门项目和参赛项目都打算在Web前端实现一个计算量很大的模型,但由于Web渲染的特性,如果其中有一个计算量很大的函数,就会阻塞事件队列,导致界面像卡死了一样无法操作,这会严重影响使用体验,所以就在寻找多线程的解决方案,本文会介绍一些个人的踩坑经历和对部分多线程解决方案的理解。
在此先感谢知乎@湖心月 大佬,他写的Comlink的文章给了我很大的启发,并在我使用Comlink踩坑的时候指明了解决问题的方向,真的非常的感谢!
文中使用到的资料会进行标注,文末会放上参考资料的列表。
一、Web Worker
Web Worker是我所接触到的Web多线程解决方案里最早的一个,也是后来其他解决方案的原典。
下面是Web Worker的部分用法示例,
主线程:
//主线程采用new命令,调用Worker()构造函数,新建一个 Worker 线程
var worker = new Worker('work.js');
//然后,主线程调用worker.postMessage()方法,向 Worker 发消息
worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});
//接着,主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息
worker.onmessage = function (event) {
console.log('Received message ' + event.data);
doSomething();
}
function doSomething() {
// 执行任务
worker.postMessage('Work done!');
}
//Worker 完成任务以后,主线程就可以把它关掉
worker.terminate();
Worker线程:
//Worker 线程内部需要有一个监听函数,监听message事件
self.addEventListener('message', function (e) {
self.postMessage('You said: ' + e.data);
}, false);
//上面代码中,self代表子线程自身,即子线程的全局对象。因此,等同于下面两种写法
// 写法一
this.addEventListener('message', function (e) {
this.postMessage('You said: ' + e.data);
}, false);
// 写法二
addEventListener('message', function (e) {
postMessage('You said: ' + e.data);
}, false);
//根据主线程发来的数据,Worker 线程可以调用不同的方法
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
self.postMessage('WORKER STARTED: ' + data.msg);
break;
case 'stop':
self.postMessage('WORKER STOPPED: ' + data.msg);
self.close(); // Terminates the worker.
break;
default:
self.postMessage('Unknown command: ' + data.msg);
};
}, false);
Web Worker虽然看上去比较简洁明了,但实际使用中对于我这样从Vue开始接触前端开发的萌新来说,还是比较抽象的,而且使用中也有不少限制,如下文所示[1]:
(1)同源限制
分配给 Worker 线程运行的脚本文件,必须与主线程的脚本文件同源。
(2)DOM 限制
Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的 DOM
对象,也无法使用document、window、parent这些对象。但是,Worker
线程可以navigator对象和location对象。(3)通信联系
Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成。
(4)脚本限制
Worker 线程不能执行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
(5)文件限制
Worker 线程无法读取本地文件,即不能打开本机的文件系统(file://),它所加载的脚本,必须来自网络。
不过Web Worker由于提出的时间早,所以兼容性很好,如果想兼容低版本浏览器的话,Worker不失为一个良好的解决方案。
二、Comlink
Worker的踩坑导致我一度对多线程方案丧失了信心,但在模型即将完成之际,又重燃了实现多线程的想法,于是碰到了让我大受启发的@湖心月 大佬写的关于Comlink的文章。
Comlink是Google的Surma所设计的更现代化的API,更符合现在开发人员的使用习惯。@湖心月 文中对Worker的优缺点有着更深层次的理解和描述,并介绍了Comlink的用例。
以下是Comlink的官方给出的一个示例,一个简单的计数器。
// main.js
import * as Comlink from "https://unpkg.com/comlink?module";
const worker = new Worker("worker.js");
// This `state` variable actually lives in the worker!
const state = await Comlink.wrap(worker);
await state.inc();
console.log(await state.currentCount);
// worker.js
import * as Comlink from "https://unpkg.com/comlink?module";
const state = {
currentCount: 0,
inc() {
this.currentCount++;
}
}
Comlink.expose(state);
示例就是如此简洁,看完很快就能上手。
@湖心月 大佬对Comlink的部分描述
Comlink精妙的地方,我个人认为在于将数据传递的操作变成了一个异步的操作,这样我们就能很好的利用ES6所提供的async/await语法糖,将数据的传递与接收逻辑写得非常简洁优雅。开发者不需要再去考虑事件订阅所带来的各种复杂度。
他的文章中还有在Vue或Vue+Vuex环境下的例子,感兴趣的可以去看看,链接在文末。
三、Comlink-loader
Comlink-loader是尝试在Vue-cli构建的项目下引入Comlink失败而找到的办法(来自于@湖心月 大佬的提示)
下面就直接介绍comlink-loader的使用方法
安装
npm install -D comlink-loader
使用
代码如下(示例):
//TestFun.js
export class MyClass{
async inc(count) {
for (let i = 0; i<10000; i++){
for(let j = 0; j<30000; j++){
count += i+j
}
}
return count;
}
}
//main
import MyWorker from 'comlink-loader!../../TestFun'
export default {
data() {
return {
counter: 0,
continuedCount: 0
}
},
methods: {
countP1(){
this.counter++
},
async continuedCountP(){
const count = this.continuedCount;
const inst = new MyWorker();
const obj = await new inst.MyClass();
this.continuedCount = await obj.inc(count)
}
},
}
‘comlink-loader!../…/TestFun’中的’…/…/TestFun’是TestFun.js与main.vue的相对位置,TestFun不需要加后缀。为了防止有像我一样的小白看不懂,特地说明一下。
此处介绍的是comlink-loader的默认使用方式,comlink-loader还有另一种Singleton Mode(单例模式),可到git仓库中查看。
总结
本文仅仅简单介绍了comlink-loader的使用,而关于Worker的包还有vue-worker、worker-loader等内容,感兴趣的可以自行去了解。
目前仅实现了简单的例子,日后把模型给应用上之后,也许会来更新一些使用中碰到的坑。
参考资料
[1]Web Worker 使用教程@阮一峰
http://www.ruanyifeng.com/blog/2018/07/web-worker.html
[2]如何无痛的为你的前端项目引入多线程@湖心月
https://zhuanlan.zhihu.com/p/146374834
[3]vue-worker:在vue中方便使用web worker
https://www.tangshuang.net/3657.html
[4]https://github.com/GoogleChromeLabs/comlink-loader
更多推荐
所有评论(0)