@kuiu/task-worker
Version:
task worker
433 lines (420 loc) • 15 kB
text/typescript
/**
* @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("-");
// }