UNPKG

@kuiu/task-worker

Version:

task worker

433 lines (420 loc) 15 kB
/** * @namespace 任务调度器 * @description 负责将耗时任务拆分到多个线程中执行 * 重新设计,基于任务调度的方式 使用多线程 这个可以单独设计多个模块(任务调度和任务切分模块, 真的有DOM 在线程中使用模拟代理的模式) * A 主线程负责通信,调度和消费消息 * B 线程 负责解析实体 * C 线程 负责解析实体 * B C 线程 解析不重复 */ // import { BSON } from 'bson' // @ts-ignore // export const IsWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope // export const HasSharedBuffer = globalThis.SharedArrayBuffer != null export const ProxyMarker = Symbol.for("TaskProxy.proxy"); export const ArgsTransfer = Symbol.for("TaskProxy.args.transfer"); export const ReturnTransfer = Symbol.for("TaskProxy.return.transfer"); export const NowaitMarker = Symbol.for("TaskProxy.nowait"); export const ThrowMarker = Symbol.for("TaskProxy.thrown"); export const enum MessageType { GET = 1, SET = 2, APPLY = 3, CONSTRUCT = 4, ENDPOINT = 5, RELEASE = 6, INIT = 7, } export const enum WireValueType { RAW = 7, PROXY = 8, THROW = 9, HANDLER = 10, } export type Message = { id?: string | number; op: MessageType; path?: string[]; args?: WireValue[]; value?: WireValue[]; scope?: any; } export type PathKey = (string | number | symbol); export type MsgParams = { type: string, path?: PathKey[], args?: any[], [key: string]: any }; export type WireValue = { id?: string | number; return?: boolean; isError?: boolean; scope?: any; type: WireValueType; value: unknown; } /** * * @param value * @param argsTransfer 表示该函数被操作时,处理参数和值 * @param returnTransfer 表示该函数被操作时,处理参数和值 * @param nowait * @returns */ export function proxy(value: any, argsTransfer?: string | null, returnTransfer?: string | null, nowait?: boolean) { return Object.assign(value, { [ProxyMarker]: true, [ArgsTransfer]: argsTransfer ? argsTransfer : null, [ReturnTransfer]: returnTransfer ? returnTransfer : null, [NowaitMarker]: !!nowait }) } export class TaskWorker { id: number; threads: number; worker: globalThis.Worker; waitMap = new Map<string | number, [Function, Function]>(); proxyRegistry = new Map<any, any>() proxyCache: any = {}; isAutoProxy: boolean = true; // isSync: boolean; RunInstance?: any; _initing: Promise<any> | null = null; _finalizers: any; _UID: number = 0; [key: `${string}Transfer`]: Function; constructor(worker: Worker, id: number, threads = 1) { this.id = id; this.threads = threads; this.worker = worker; // this.isSync = isSync; this.worker.addEventListener('message', this.msgHandler); this._finalizers = "FinalizationRegistry" in globalThis ? new globalThis.FinalizationRegistry((val: any) => { this.postMessage({ op: MessageType.RELEASE, scope: val, }) this._finalizers.unregister(this.proxyRegistry.get(val)); this.proxyRegistry.delete(val); }) : null as any } expose(key: string = 'default', value: object) { this.proxyCache[key] = value; } initial = (param?: any) => { this._initing = this.postMessage({ op: MessageType.INIT, }, { id: this.id, threads: this.threads, param }); return this; } terminate() { try { this.RunInstance?.close?.(true); } catch (error) { } this.RunInstance = null; return this.worker.terminate(); } postMessage = async (msg: any, rawValue?: any, transfer?: string, isReturn?: boolean) => { if (this._initing != null) { await this._initing; this._initing = null; } msg = Object.assign(this._toWireValue(rawValue), msg) if (msg.args != null) { // 是否启动自动转换机制 function 非代理类型 msg.args = msg.args.map((arg: any) => this._toWireValue(arg)) as any[]; } const id = msg.id ??= ++this._UID; // @ts-ignore this.worker.postMessage(msg, (transfer ? this[transfer as string]?.(msg, isReturn) : undefined)); return this._promise(id); } msgHandler = (e: MessageEvent) => { if (e.data.return) { this.returnBody(e.data); } else { this.execBody(e.data); } } /** * @description 接收来自执行函数的返回结果 * @param data * @returns */ returnBody(data: WireValue) { const id = data?.id; const promiseFn: any = id == null ? null : this.waitMap.get(id); if (promiseFn == null) return; this.waitMap.delete(id as any); // 接收执行返回的参数=> 解析成正常值 Promise.resolve().then(() => { // 执行_fromWireValue 可能 返回或者抛出一个错误 return this._fromWireValue(data); }).then(...promiseFn); } /** * @description 接收方执行调用消息 * @param data * @returns */ execBody(data: Message) { const { id, op } = data; const path = data.path || []; // 对参数接收的参数进行转换 const args = (data.args || []).map(this._fromWireValue); // 先临时使用 const target = this.proxyCache[data.scope ?? 'default'] || this; const last = path[path.length - 1]; const noPath = last == null; let returnValue, transfer: any; try { // 如果访问不到是否执行初始化 proxyCache const parent = noPath ? target : path.slice(0, -1).reduce((target: any, prop: string | number) => target[prop], target); const rawValue = noPath ? target : parent[last]; const nowait = rawValue?.[NowaitMarker] === true; if (nowait) this.postMessage({ id, return: true }); switch (op) { case MessageType.APPLY: { returnValue = rawValue.apply(parent, args); transfer = rawValue[ReturnTransfer] ?? target[ReturnTransfer]; } break; case MessageType.GET: { returnValue = rawValue; transfer = rawValue?.[ReturnTransfer] ?? target[ReturnTransfer]; } break; case MessageType.SET: { parent[last] = this._fromWireValue(data as any); returnValue = true; } break; case MessageType.CONSTRUCT: { returnValue = proxy(new rawValue(...args)); } break; case MessageType.RELEASE: { // 是否需要改成 = null的形式 this.proxyCache[data.scope] = null; returnValue = undefined; } break; case MessageType.INIT: { const value = this._fromWireValue(data as any) this.id = value.id; this.threads = value.threads; returnValue = noPath ? true : (rawValue?.initial?.(value.param) || true); } break; // default: // return; } if (nowait) return; } catch (value) { returnValue = value; } // Promise.resolve(returnValue).catch((value) => value).then((value) => { Promise.resolve(returnValue).then((value) => { // 返回结果端是否也需要 transfer 怎么设计 this.postMessage({ id, return: true }, value, transfer, true); }).catch((error) => { this.postMessage({ id, return: true }, error); }); } /** * * @param 接收后进行检查转换 * @returns */ private _fromWireValue = (data: WireValue) => { switch (data.type) { case WireValueType.HANDLER: if (data.isError) { throw Object.assign( new Error(''), data.value ); } // 接收的是个代理对象 else { /**@ts-ignore */ data[ProxyMarker] = true; data.scope = data.value; const token: any = []; const tproxy: any = createTaskPorxy([this as any], [], data, true); this._finalizers?.register(tproxy, data.scope, token); this.proxyRegistry.set(data.scope, token); return tproxy } case WireValueType.RAW: return data.value; } } /** * * @param 发送前进行转换格式 * @returns */ private _toWireValue = (value: any) => { if (isProxyMarker(value)) { // 需要先缓存起来 const scope = ++this._UID; this.proxyCache[scope] = value; return { type: WireValueType.HANDLER, transfer: value[ArgsTransfer], // 需要生产唯一的值 和子线程的这个值对应 value: scope, } } // 自动转换 if (this.isAutoProxy && typeof value === 'function') { // if (isFunction(value)) { // 需要先缓存起来 const scope = ++this._UID; this.proxyCache[scope] = value; return { type: WireValueType.HANDLER, transfer: value[ArgsTransfer], value: scope, } } if (isErrorMarker(value)) { return { type: WireValueType.HANDLER, isError: true, value: { message: value.message, name: value.name, stack: value.stack, }, } } return { type: WireValueType.RAW, value, } } private _promise(id: string | number) { return new Promise((resolve, reject) => { this.waitMap.set(id, [resolve, reject]); }) } } /** * * @param threads * @param path * @param target * @param inited * @returns */ export function createTaskPorxy( threads: (globalThis.Worker | string)[], path: PathKey[] = [], target?: any, inited?: boolean, param?: any, // isSync?: boolean ) { const Workers: any[] = inited ? threads : threads.map((worker: any, index) => { return new TaskWorker(typeof worker === 'string' ? new Worker(worker) : new worker(), index, threads.length).initial(param); }); const before = target?.before; // const after = target?.after || (isSync ? defaulSynctAfter : defaultAfter); const after = target?.after || defaultAfter; const scope = target?.[ProxyMarker] && (target.scope ?? null); // 是否收集transfers 数据(调用端处理参数) const transfer = target?.transfer; const tproxy: any = new Proxy(function () { }, { get(_target, prop) { if (prop === '__target') { return target; // 返回关联的原始对象 } if (before?.(MessageType.GET, path, prop)) { return Reflect.get(path.reduce((obj, key) => obj[key], target), prop); } if (prop === "then") { if (path.length === 0) { return { then: () => tproxy }; } const r = after(Workers, { op: MessageType.GET, // 需要知道那个对象执行体 scope, path, }) return r.then.bind(r); } return createTaskPorxy(Workers, [...path, prop], target, true); }, set(_target, prop, rawValue) { if (before?.(MessageType.SET, path, prop)) { return Reflect.set(path.reduce((obj, key) => obj[key], target), prop, rawValue) } return after(Workers, { op: MessageType.SET, // 需要知道那个对象执行体 scope, path: [...path, prop], }, rawValue, transfer) as any; }, apply(_target, _thisArg, args) { const last = path[path.length - 1]; if (last === "bind") { return createTaskPorxy(Workers, path.slice(0, -1), target, true); } if (last === "call" || last === "apply") { path = path.slice(0, -1); if (!isProxyMarker(args[0])) { args = args.slice(1); } } return after(Workers, { // 需要知道那个对象执行体 op: MessageType.APPLY, scope, path, args, }, undefined, transfer) as any; }, construct(_target, args) { return after(Workers, { op: MessageType.CONSTRUCT, // 需要知道那个对象执行体 scope, path, args, }, undefined, transfer) as any; }, }) // target.proxy ??= tproxy; return tproxy; } function defaultAfter(workers: TaskWorker[], params: MsgParams, rawValue?: any, transfer?: string) { return Promise.all(workers.map((worker) => { return worker.postMessage(params, rawValue, transfer) })).then((args: any[]) => { // 是否考虑合并 return args.length > 1 ? args : args[0]; }) } function isObject(val: unknown): val is object { return (typeof val === "object" && val !== null) || typeof val === "function" } function isProxyMarker(val: any): boolean { /**@ts-ignore */ return isObject(val) && val[ProxyMarker] } function isErrorMarker(val: any): boolean { /**@ts-ignore */ return (isObject(val) && val[ThrowMarker]) || val instanceof Error } // function generateUUID(): string | number { // // return new Array(4).fill(0) // // .map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)) // // .join("-"); // }