大家好,我是梦兽。一个技术分享宅男。欢迎大家订阅我的b站频道。 傻梦兽的个人空间_哔哩哔哩_bilibili
最近梦兽,我在对接一个项目。这个项目的”上一任”运用了webworker。可是它运用的这个webworker有点复杂让咱们团队几个人看也看了很久,是以面向对象的思路去做,但许多当地为了封装而封装,然让阅读起来十分困难。
咱们想看看他原来是怎样运用的web woker。
留意: 假如你的项目没运用webpack-loacker和vite的话,我主张你不用运用woker。由于web woker需求单独编译文件!
ThreadPool 部分重构
先看看”上一任”写的Manage,我估计它是想写线程池的。可是在Manage之后需求履行init。
在init办法里边做了许多注册办法.这样导致这个线程池运用起来很纯!
看到这儿我现已知道了,这个不是woker,只能说他运用了一个woker,然后处理了websocket的音讯。
所以导致他需求在运用层,需求运用一个map运用时间戳来读取,所以需求把woker的onmessage写在调用的当地,进行set和get进行音讯的消费,然后再履行回调函数,这样做实在是太蠢,而且增加了阅读的本钱。
假如咱们来封装的话,咱们能够先写真正的写一个线程池thead_pool.ts,内部就能自己消费message即可。
type ThreadPoolQueue = [(value: any) => void, (reason: any) => void, any[]];
export class ThreadPool {
private readonly workers: Set<Worker>;
private readonly freeWorkers: Worker[];
private readonly queue: ThreadPoolQueue[] = [];
/**
* 创建一个线程池
* @param fn 线程池要履行的函数,它不行带有任何闭包变量,且只能运用有限的函数。
* 详见 https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope
* @param size 线程个数(最大并发数,有必要为大于 0 的整数)
*/
constructor(worker: Worker, size = navigator.hardwareConcurrency - 1) {
if (size < 1) throw new RangeError("size must grater than 0");
this.freeWorkers = Array.from({ length: size }, () => worker);
this.workers = new Set(this.freeWorkers);
}
/**
* 当有线程空余时,将参数转发至线程,开端履行。
* 当没有线程空余时,将参数追加至调度行列,等候其他线程空余。
* @param args 传入线程函数的参数。留意它们会以结构化克隆的方式传入(相似深复制),而非一般的引证传值。
* https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
* @returns Promise,当线程函数履行结束后 resolve 其返回值
*/
dispatch(...args: any[]) {
return new Promise<any>((resolve, reject) => this.start(resolve, reject, args));
}
/**
* 立即结束一切线程,释放资源。
* 留意:本函数会强制中止正在运行中的线程,并 reject 一切等候中的 promise
*/
dispose() {
}
/**
* 获得当时空闲的线程个数
*/
getFreeWorkerCount() {
return this.freeWorkers.length;
}
/**
* 获得当时在行列中等候的工作个数
*/
getWaitingEventCount() {
}
/// 私有办法
private onFinish(worker: Worker) {
worker.onmessage = null as any;
worker.onerror = null as any;
this.freeWorkers.push(worker);
if (this.queue.length) {
this.start(...this.queue.shift() as ThreadPoolQueue);
}
}
private start(resolve: (value: any) => void, reject: (reason: any) => void, args: any[]) {
if (this.freeWorkers.length) {
const worker = this.freeWorkers.pop() as Worker;
worker.onmessage = e => {
this.onFinish(worker);
resolve(e.data);
};
worker.onerror = e => {
this.onFinish(worker);
reject(e.error);
};
worker.postMessage(args);
} else {
this.queue.push([resolve, reject, args]);
}
}
}
当我咱们有了以上的代码之后,咱们就不需求把咱们的woker写在主线程文件一起阅读。 那咱们要如何是运用呢? 也是需求写一份woker文件。 然后你想运用这个woker 只需求。
this.write_pool = new ThreadPool(
// 线程池要履行的函数,它不行带有任何闭包变量,且只能运用有限的函数。 [DedicatedWorkerGlobalScope - Web API 接口参考 | MDN (mozilla.org)](https://developer.mozilla.org/zh-CN/docs/Web/API/DedicatedWorkerGlobalScope)
new WebWoker(),
// 协程数创建多少个worker进行通讯
2);
this.write_pool.dispatch(
// 运用worker 处理的数据
this.rpcdrive.sendMessage(data) ).then(res => {
// 当线程函数履行结束后 resolve 其返回值
console.log(res);
})
这个时候就现已把thread pool进行抽离。你想写的任何线程都能够直接相似Java的ThreadPool那种进行start。提高了咱们的可读性你想知道做了什么工作就直接去看Worker里边看就好了。
咱们看一下重构后的作用,咱们现在能够在开不同的线程进行读写,而不是想之前读写都在一个线程里边进行。
音讯序列化和放序列号 部分重构
咱们再看看对blod二进制进行的操作,存在很大一部分为了封装而封装。
由于没有上面所说的自己消费queue,还需求一个controller进行办理音讯的创建然后转换数据blob给服务器。
当我看懂了这部分代码后,他做了这么多工作,其实实际两个文件就能够做完。
总结
代码是写起来跑的,还有另一面是让人更好的了解。不要为了面向对象封装,过度的封装并不是一件很好的工作。