我正在尝试异步运行一段JavaScript代码到主线程。我不一定需要代码在不同的线程上实际运行(因此性能不需要比顺序执行更好),但我希望代码与主线程并行执行,这意味着没有冻结。
此外,所有需要的代码都需要包含在一个函数中。
我的示例工作量如下:
function work() {
for(let i=0; i<100000; i++)
console.log("Async");
}
此外,我可能在主线程上有一些工作(允许冻结侧面,只是为了测试):
function seqWork() {
for(let i=0; i<100000; i++)
console.log("Sequential");
}
预期输出应该是这样的:
Sequential
Async
Sequential
Sequential
Async
Sequential
Async
Async
...
你明白了。
免责声明:我对JavaScript以及使用async
和await
完全没有经验。
我尝试过的
我做了一些研究,发现了这3个选项:
1.async
/await
似乎是显而易见的选择。所以我尝试了这个:
let f= async function f() {
await work();
}
f();
seqWork();
输出:
Async (100000)
Sequential (100000)
我也试过:
let f = async function f() {
let g = () => new Promise((res,rej) => {
work();
res();
});
await g();
}
f();
seqWork();
输出:
Async (100000)
Sequential (100000)
所以这两种方法都不起作用。它们还在异步输出期间冻结浏览器,所以这似乎完全没有效果(?)我可能在这里做错了什么,但我不知道是什么。
2.承诺.all
这似乎被誉为任何昂贵任务的解决方案,但只有当你有很多阻塞任务并且你想"组合";将它们转换为一个阻塞任务,这比按顺序执行它们更快。当然有这样的用例,但对于我的任务来说,这是无用的,因为我只有一个异步执行的任务;线程";应该在该任务期间继续运行。
3.工人
在我看来,这似乎是最有希望的选择,但我还没有让它发挥作用。主要问题是您似乎需要第二个脚本。我无法做到这一点,但即使在使用第二个文件进行本地测试时,Firefox也会阻止该脚本的加载。
这是我尝试过的,我在研究中没有发现任何其他选择。我开始认为这样的东西在JS中是不可能的,但这似乎是一项非常简单的任务。同样,我不需要实际上并行执行,如果事件循环在从主线程调用语句和异步";线程";。来自Java,它们还能够在单个硬件线程上模拟多线程。
编辑:上下文
我有一些java代码可以使用TeaVM转换为JavaScript(我无法控制转换)。Java本机支持多线程,我的许多代码都依赖于这一点。现在,由于JavaScript显然并不真正支持真正的多线程,TeaVM以最简单的方式将Thread转换为JS:调用Thread.start()
直接调用Thread.run()
,这使得它完全不可用。我想在这里创建一个更好的多线程仿真,它基本上可以在不修改的情况下执行线程代码。现在它并不理想,但是插入";产生";语句转换为java代码是可能的。
TeaVM有一个方便的功能,允许您编写用匹配的JS代码注释的本地Java方法,这些代码将直接转换为该代码。问题是,您无法设置方法体,因此无法使其成为异步方法。
我现在尝试做的一件事是实现一个JS原生";产量"/"暂停";(不要在JS中使用关键字)函数,我可以调用它来允许其他代码直接从java方法运行。该方法基本上必须短暂地阻止调用代码的执行,而不是调用其他排队任务的执行。我不确定如果主代码不在异步函数中,这是否可能。(我无法更改生成的JS代码)
我能想到的解决这个问题的唯一其他方法是让JS方法调用所有的阻塞代码,并引用Java代码。然而,主要的问题是,这意味着将java方法的方法体拆分成许多小块,因为java不支持C#中的yield return
。这基本上意味着对每一段并行执行的代码都要进行彻底的返工,我会极力避免这种情况。此外,你不能";产量;从一个调用的方法中,使其模块化程度大大降低。在这一点上,我还可以直接从内部事件循环调用Java中的方法块。
由于JavaScript是单线程的,因此可以在之间进行选择
- 在主线程中异步运行一些代码,或者
- 在工作线程中运行相同的代码(即而不是主线程的线程)
协同多任务
如果您想在主线程中运行大量代码而不进行过度阻塞,则需要协同编写多任务代码。这需要长时间运行同步任务,以便定期将控制权交给任务管理器,以允许其他任务运行。就JavaScript而言,您可以通过在异步函数中运行任务来实现这一点,该函数定期等待短时间的系统计时器。这是有潜力的,因为await
保存当前执行上下文,并在执行异步任务时将控制权返回给任务管理器。计时器调用确保任务管理器在将控制权返回到启动计时器的异步任务之前,可以实际循环并执行其他操作。
等待已经实现的承诺只会交错执行微任务队列中的作业,而不会返回到正确的事件循环,不适合用于此目的。
调用代码模式:
doWork()
.then( data => console.log("work done"));
工作代码:
async function doWork() {
for( i = 1; i < 10000; ++i) {
// do stuff
if( i%1000 == 0) {
// let other things happen:
await new Promise( resolve=>setTimeout(resolve, 4))
}
}
}
请注意,这借鉴了历史实践,可能适合快速运行原型代码的目的。我不认为它特别适合商业生产环境。
工作线程
本地主机服务器可以用于从URL提供工作程序代码,以便可以继续进行开发。一种常见的方法是使用一个节点/express服务器侦听一个称为localhost的环回地址端口。
您需要安装node,并使用NPM(与node一起安装)安装express。我无意进入node/express生态系统——网上有很多关于它的材料。
如果您仍在寻找一个最低限度的静态文件服务器来提供当前工作目录中的文件,下面是我之前写的一个。同样,网上也有许多类似的例子。
"use strict";
/*
* express-cwd.js
* node/express server for files in current working directory
* Terminal or shortcut/desktop launcher command: node express-cwd
*/
const express = require('express');
const path = require('path');
const process = require("process");
const app = express();
app.get( '/*', express.static( process.cwd())); // serve files from working directory
const ip='::1'; // local host
const port=8088; // port 8088
const server = app.listen(port, ip, function () {
console.log( path.parse(module.filename).base + ' listening at http://localhost:%s', port);
})
承诺延迟
内联的promise延迟如";工作代码";上面可以写成一个函数,而不叫yield,yield是一个保留字。例如
const timeOut = msec => new Promise( r=>setTimeout(r, msec));
分段执行阻塞代码的示例:
"use strict";
// update page every 500 msec
const counter = document.getElementById("count");
setInterval( ()=> counter.textContent = +counter.textContent + 1, 500);
function block_500ms() {
let start = Date.now();
let end = start + 500;
for( ;Date.now() < end; );
}
// synchronously block for 4 seconds
document.getElementById("block")
.addEventListener("click", ()=> {
for( var i = 8; i--; ) {
block_500ms();
}
console.log( "block() done");
});
// block for 500 msec 8 times, with timeout every 500 ms
document.getElementById("block8")
.addEventListener("click", async ()=> {
for( var i = 8; i--; ) {
block_500ms();
await new Promise( resolve=>setTimeout(resolve, 5))
}
console.log("block8() done");
});
const timeOut = msec => new Promise( r=>setTimeout(r, msec));
document.getElementById("blockDelay")
.addEventListener("click", async ()=> {
for( var i = 8; i--; ) {
block_500ms();
await timeOut();
}
console.log("blockDelay(1) done");
});
Up Counter: <span id="count">0</span>
<p>
<button id="block" type="button" >Block counter for 4 seconds</button> - <strong> with no breaks</strong>
<p>
<button id="block8" type="button" >Block for 4 seconds </button> - <strong> with short breaks every 500 ms (inline)</strong>
<p>
<button id="blockDelay" type="button" >Block for 4 seconds </button> - <strong> with short breaks every 500 ms (using promise function) </strong>
一些急动可能在块代码的交错部分中是明显的,但可以避免完全冻结。超时值由实验决定——以可接受的方式工作的值越短越好。
注意
程序设计必须确保保存输入数据、中间结果和累积输出数据的变量不会被主线程代码破坏,这些代码可能在繁重的代码执行过程中执行,也可能不执行。