我有多个长时间运行的任务,例如超过 10 毫秒,这会影响浏览器的响应能力。最糟糕的事情,例如从文件加载和解析 3D 模型,已经被卸载到 Web Workers,这样它们就不会影响渲染循环。
然而,有些任务不容易移植到 Workers,因此必须分布在主线程中的多个框架上。我不想一次性完成 1 秒的任务,而是将其分成约 5 毫秒的包,以便浏览器有机会在其间执行其他事件(鼠标移动、requestAnimationFrame 等)。
生成器函数与 setTimeout 相结合似乎是最简单的方法。我已经破解了一些可以完成这项工作的东西,但我想知道是否有更好/更干净的方法来解决这个问题。
下面的代码计算 1 亿次 Math.random() 调用的平均值。
第一个版本一次性计算平均值,但会使浏览器停顿约 1.3 秒。
第二个版本滥用生成器函数,在每 500 万个点后产生一次,从而使浏览器有机会在其间执行其他事件(鼠标移动)。通过 setTimout 循环重复调用生成器函数,直到处理完所有 1 亿个样本。
<html>
<head></head>
<body>
<script>
let samples = 100 * 1000 * 1000;
{ // run complete task at once, possibly stalling the browser
function run(){
let start = performance.now();
let sum = 0.0;
for(let i = 0; i < samples; i++){
sum = sum + Math.random();
}
let mean = sum / samples;
let duration = performance.now() - start;
console.log(`single-run: duration: ${duration}`);
console.log(`single-run: sum: ${sum}`);
console.log(`single-run: mean: ${mean}`);
}
run();
}
{ // run in smaller packages to keep browser responsive
// move mouse to check if this callback is executed in between
document.body.addEventListener("mousemove", () => {
console.log("mouse moved");
});
function * distributedRun(){
let start = performance.now();
let packageSize = 5 * 1000 * 1000;
let sum = 0.0;
for(let i = 0; i < samples; i++){
sum = sum + Math.random();
if((i % packageSize) === 0){
yield sum;
}
}
let mean = sum / samples;
let duration = performance.now() - start;
console.log(`distributed-run: duration: ${duration}`);
console.log(`distributed-run: sum: ${sum}`);
console.log(`distributed-run: mean: ${mean}`);
yield sum;
}
let generatorInstance = distributedRun();
function loop(){
let result = generatorInstance.next();
console.log(`distributed-run intermediate result: ${result.value}`);
if(!result.done){
setTimeout(loop, 0);
}
}
loop();
}
</script>
</body>
</html>
ES2018 有异步迭代器,听起来有点像我正在寻找的东西,但我不确定它们是否真的适用于此类问题。像这样使用它仍然会导致浏览器停止运行:
for await (const result of distributedRun()) {
...
}
(在 runDistributed() 函数中尝试了一些异步,但说实话,我仍在学习等待/异步的细节)