我正在尝试与主线程异步运行一段 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
.
我试过的
我做了一些研究,发现了这三个选项:
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. Promise.all
这似乎被称赞为任何昂贵任务的解决方案,但如果您有许多阻塞任务并且您想将它们“组合”成一个比顺序执行更快的阻塞任务,这似乎是一个合理的选择。这当然有用例,但对于我的任务来说它是无用的,因为我只有一个任务要异步执行,并且主“线程”应该在该任务期间继续运行。
3. 工人
在我看来,这似乎是最有希望的选择,但我还没有让它发挥作用。主要问题是您似乎需要第二个脚本。我不能这样做,但即使在使用第二个文件进行本地测试时,Firefox 也会阻止加载该脚本。
这是我尝试过的,我在研究中没有找到任何其他选择。我开始认为这样的事情在 JS 中是不可能的,但这似乎是一个非常简单的任务。同样,我不需要实际并行执行它,如果事件循环在从主线程调用语句和异步“线程”之间交替调用就足够了。来自 Java,它们还能够在单个硬件线程上模拟多线程。
编辑:上下文
我有一些使用 TeaVM 转换为 JavaScript(我无法控制转换)的 java 代码。Java 本身就支持多线程,我的很多代码都依赖于这种可能性。现在由于 JavaScript 显然并不真正支持真正的多线程,TeaVM 以最简单的方式将 Thread 转换为 JS:Thread.start()
直接调用Thread.run()
使得它完全无法使用。我想在这里创建一个更好的多线程仿真,它几乎可以在不修改的情况下执行线程代码。现在它并不理想,但可以在 java 代码中插入“yielding”语句。
TeaVM 有一个方便的功能,它允许您编写带有匹配 JS 代码注释的本地 Java 方法,这些代码将直接转换为该代码。问题是,您无法设置方法主体,因此您无法将其设为异步方法。
我现在尝试做的一件事是实现一个 JS 原生“yield”/“pause”(不使用 JS 中的关键字)函数,我可以调用该函数以允许其他代码直接从 java 方法运行。该方法基本上必须暂时阻止调用代码的执行,而是调用其他排队任务的执行。我不确定主代码不在异步函数中是否可行。(我无法更改生成的 JS 代码)
我能想到的解决此问题的唯一另一种方法是让 JS 方法调用所有阻塞代码,并参考 Java 代码。但主要问题是,这意味着将 java 方法的方法体拆分为许多小块,因为 Java 不支持yield return
C# 之类的东西。这基本上意味着对每一个并行执行的代码进行彻底的返工,我会拼命避免这种情况。此外,您不能从被调用的方法中“屈服”,从而使其模块化程度降低。那时我还不如直接从内部事件循环中调用 Java 中的方法块。