有两个不错的选择。
选项1: Worker.terminate()
第一个是杀死你现有的网络工作者并开始一个新的。为此,您可以使用Worker.terminate()
.
接口的terminate()
方法Worker
立即终止Worker
. 这不会为工人提供完成其操作的机会;它只是立即停止。
这种方法的唯一缺点是:
- 你失去了所有的工人状态。如果您必须为请求将大量数据复制到其中,则必须再次执行所有操作。
- 它涉及线程的创建和销毁,这并不像大多数人想象的那么慢,但是如果您大量终止 Web Worker,则可能会导致问题。
如果这些都不是问题,那么它可能是最简单的选择。
就我而言,我有很多状态。我的工作人员正在渲染图像的一部分,当用户平移到不同的区域时,我希望它停止正在执行的操作并开始渲染新区域。但是渲染图像所需的数据非常庞大。
在您的情况下,您拥有不想使用的(可能很大)列表的状态。
选项 2:屈服
第二种选择基本上是进行协作多任务处理。你像往常一样运行你的计算,但你时不时地暂停(yield)并说“我应该停止吗?”,就像这样(这是一些无意义的计算,而不是排序)。
let requestId = 0;
onmessage = event => {
++requestId;
sortAndSendData(requestId, event.data);
}
function sortAndSendData(thisRequestId, data) {
let isSorted = false;
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
// Check if we are still the current request ID.
if (thisRequestId !== requestId) {
// Data was changed. Cancel this sort.
return;
}
}
postMessage(total);
}
但这不会起作用,因为sortAndSendData()
运行到完成并阻止 Web Worker 的事件循环。我们需要某种方式来让thisRequestId !== requestId
. 不幸的是,Javascript 并没有一个yield
方法。它确实有async
/await
所以我们可以试试这个:
let requestId = 0;
onmessage = event => {
console.log("Got event", event);
++requestId;
sortAndSendData(requestId, event.data);
}
async function sortAndSendData(thisRequestId, data) {
let isSorted = false;
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
await Promise.resolve();
// Check if we are still the current request ID.
if (thisRequestId !== requestId) {
console.log("Cancelled!");
// Data was changed. Cancel this sort.
return;
}
}
postMessage(total);
}
不幸的是它不起作用。我认为这是因为async
/await
使用“微任务”急切地执行事情,如果可能的话,这些“微任务”会在待处理的“宏任务”(我们的网络工作者消息)之前执行。
我们需要强制我们await
成为一个宏任务,你可以使用setTimeout(0)
:
let requestId = 0;
onmessage = event => {
console.log("Got event", event);
++requestId;
sortAndSendData(requestId, event.data);
}
function yieldToMacrotasks() {
return new Promise((resolve) => setTimeout(resolve));
}
async function sortAndSendData(thisRequestId, data) {
let isSorted = false;
let total = 0;
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
await yieldToMacrotasks();
// Check if we are still the current request ID.
if (thisRequestId !== requestId) {
console.log("Cancelled!");
// Data was changed. Cancel this sort.
return;
}
}
postMessage(total);
}
这有效!然而,它非常缓慢。await yieldToMacrotasks()
在我的带有 Chrome 的机器上大约需要 4 毫秒!这是因为浏览器将最小超时设置为setTimeout(0)
1 或 4 毫秒(实际的最小值似乎很复杂)。
幸运的是,另一位用户向我指出了一种更快的方法。基本上在另一个上发送消息MessageChannel
也会产生事件循环,但不受最小延迟setTimeout(0)
的影响。这段代码有效,每个循环只需要大约 0.04 毫秒,应该没问题。
let currentTask = {
cancelled: false,
}
onmessage = event => {
currentTask.cancelled = true;
currentTask = {
cancelled: false,
};
performComputation(currentTask, event.data);
}
async function performComputation(task, data) {
let total = 0;
let promiseResolver;
const channel = new MessageChannel();
channel.port2.onmessage = event => {
promiseResolver();
};
while (data !== 0) {
// Do a little bit of computation.
total += data;
--data;
// Yield to the event loop.
const promise = new Promise(resolve => {
promiseResolver = resolve;
});
channel.port1.postMessage(null);
await promise;
// Check if this task has been superceded by another one.
if (task.cancelled) {
return;
}
}
// Return the result.
postMessage(total);
}
我对此并不完全满意 - 它依赖于postMessage()
以 FIFO 顺序处理的事件,我怀疑这是有保证的。我怀疑您可以重写代码以使其工作,即使那不是真的。