首页 > 解决方案 > 如何从 webworker 中取消 wasm 进程

问题描述

我有一个处理 Web 应用程序内部数据的 wasm 进程(从 c++ 编译)。假设必要的代码如下所示:

std::vector<JSONObject> data
for (size_t i = 0; i < data.size(); i++)
{
    process_data(data[i]);

    if (i % 1000 == 0) {
        bool is_cancelled = check_if_cancelled();
        if (is_cancelled) {
            break;
        }
    }

}

此代码基本上“运行/处理查询”,类似于 SQL 查询接口:

在此处输入图像描述

但是,查询可能需要几分钟才能运行/处理,并且在任何给定时间,用户都可以取消他们的查询。取消过程将发生在运行 wasm 的 service Worker 之外的普通 javascript/web 应用程序中。

那么我的问题是,我们如何知道用户已单击“取消”按钮并将其传达给 wasm 进程以便知道该进程已被取消以便它可以退出?使用worker.terminate()不是一个选项,因为我们需要为此保留所有加载的数据worker并且不能仅仅杀死该工作人员(它需要通过其存储的数据保持活动状态,因此可以运行另一个查询......)。

在 javascript 和 worker/wasm/c++ 应用程序之间进行通信的示例方式是什么,以便我们知道何时退出以及如何正确退出?

此外,让我们假设一个典型的查询需要 60 秒才能运行并使用 cpp/wasm 在浏览器中处理 500MB 的数据。


更新:根据一些研究(以及下面的初步答案/评论),我认为这里有以下可能的解决方案,并有一些反馈:

  1. 使用两个工作人员,一个工作人员存储数据,另一个工作人员处理数据。以这种方式可以终止处理工作者,并且数据将始终保留。可行的?并非如此,因为每当网络工作者启动时,将超过 500MB 的数据复制到网络工作者需要花费太多时间。这可以(以前)使用SharedArrayBuffer 完成,但由于一些安全问题,它的支持现在非常有限/不存在。太糟糕了,因为这似乎是迄今为止最好的解决方案,如果它得到支持......

  2. 使用Emterpreter和使用emscripten_sleep_with_yield. 可行的?不,使用 Emterpreter 时会破坏性能(在上面的文档中提到),并使所有查询减慢大约 4-6 倍。

  3. 始终运行第二个工作人员,并且在 UI 中只显示最新的。可行的?不,如果它不是共享数据结构并且数据大小为 500MB x 2 = 1GB(在现代桌面浏览器/计算机中运行时,500MB 似乎是一个很大但可以接受的大小),它可能会遇到很多 OOM 错误。

  4. 使用对服务器的 API 调用来存储状态并检查查询是否被取消。可行的?是的,尽管从每个正在运行的查询中每秒都对网络请求进行长轮询似乎相当笨拙。

  5. 使用增量解析方法,一次只解析一行。可行的?是的,但也需要大量重写解析函数,以便每个函数都支持这一点(实际的数据解析是在几个函数中处理的——过滤、搜索、计算、分组、排序等。

  6. 使用 IndexedDB 并将状态存储在 javascript 中。在 WASM 中分配一块内存,然后将其指针返回给 JavaScript。然后在那里读取数据库并填充指针。然后在 C++ 中处理您的数据。可行的?不确定,尽管如果可以实施,这似乎是最好的解决方案。

  7. [还要别的吗?]

在赏金中,我想知道三件事:

  1. 如果上述六种分析似乎普遍有效?
  2. 我还缺少其他(也许更好的)方法吗?
  3. 任何人都能够展示一个非常基本的 #6 示例 - 如果可能并且跨浏览器工作,这将是最好的解决方案。

标签: javascriptc++web-workeremscriptenwebassembly

解决方案


对于 Chrome(仅限),您可以使用共享内存(共享缓冲区作为内存)。当你想停下来时,在记忆中竖起一面旗帜。不是该解决方案的忠实拥护者(复杂且仅在 chrome 中受支持)。它还取决于您的查询是如何工作的,以及是否有冗长的查询可以检查标志的地方。

相反,您可能应该多次调用 c++ 函数(例如,对于每个查询)并检查您是否应该在每次调用后停止(只需向工作人员发送消息以停止)。

我所说的多次是指分阶段进行查询(单个查询的多个函数调用)。它可能不适用于您的情况。

无论如何,AFAIK 没有办法向 Webassembly 执行发送信号(例如 Linux kill)。因此,您必须等待操作完成才能完成取消。

我附上了一个可以解释这个想法的代码片段。

worker.js:

... init webassembly

onmessage = function(q) {
	// query received from main thread.
	const result = ... call webassembly(q);
	postMessage(result);
}

main.js:

const worker = new Worker("worker.js");
const cancel = false;
const processing = false;

worker.onmessage(function(r) {
	// when worker has finished processing the query.
	// r is the results of the processing.
	processing = false;

	if (cancel === true) {
		// processing is done, but result is not required.
		// instead of showing the results, update that the query was canceled.
		cancel = false;
		... update UI "cancled".
		return;
	}
	
	... update UI "results r".
}

function onCancel() {
	// Occurs when user clicks on the cancel button.
	if (cancel) {
		// sanity test - prevent this in UI.
		throw "already cancelling";
	}
	
	cancel = true;
	
	... update UI "canceling". 
}

function onQuery(q) {
	if (processing === true) {
		// sanity test - prevent this in UI.
		throw "already processing";
	}
	
	processing = true;
	// Send the query to the worker.
	// When the worker receives the message it will process the query via webassembly.
	worker.postMessage(q);
}

从用户体验的角度来看一个想法:您可以创建〜两个工人。这将占用两倍的内存,但允许您“立即”“取消”一次。(这只是意味着在后端,第二个工作人员将运行下一个查询,当第一个工作人员完成取消时,取消将再次立即生效)。


推荐阅读