我正在尝试在 v8 (rusty_v8) 之上开发一个简单的 js 运行时,但我在异步方面遇到了一些麻烦。
我有一个函数,它是 js 领域的入口点:
fn message_from_worker(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_ret: v8::ReturnValue,
)
// ...
此函数从 js 领域获取不同类型的消息,并用它们执行不同的操作。其中一条消息是执行长时间运行任务(获取)的请求。我希望任务完成后能够将消息发送回 js 领域。
到目前为止,这就是我所拥有的:
fn message_from_worker ... {
// ...
let message = args.get(0);
let message = message.to_object(scope).unwrap();
let kind = utils::get(scope, message, "kind").to_rust_string_lossy(scope);
match kind.as_str() {
// ...
"fetch" => {
let request = utils::get(scope, message, "request");
let request = request.to_object(scope).unwrap();
let url = utils::get(scope, request, "url").to_rust_string_lossy(scope);
let callback = utils::get(scope, message, "sendResponse");
let callback = match v8::Local::<v8::Function>::try_from(callback) {
Ok(callback) => callback,
Err(_) => {
utils::throw_type_error(scope, "sendResponse is not a function");
return;
}
};
// We want to perform our async http request here
let response = { /* for now response is a sync mock */ };
let undefined = v8::undefined(scope).into();
callback.call(scope, undefined, &[response.into()]);
}
// ...
天真的方法(使用阻塞请求)不起作用:
reqwest::blocking::get(&url).unwrap()
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.
所以我正在考虑在全局状态下注册所有挂起的操作,然后从主线程轮询它们。但我不知道该怎么做。
我什至不知道是否可以同时轮询多个操作,处理其中一个操作的分辨率,然后继续轮询其余操作。
要使用 V8 运行时处理 Rust 应用程序中的异步操作,您可以利用 Tokio 运行时以及
tokio::task
模块进行异步任务管理。总体思路是为长时间运行的操作生成异步任务并等待其完成,而不会阻塞 V8 运行时。
以下是使用
tokio
和 reqwest
包进行异步 HTTP 请求的示例:
use rusty_v8 as v8;
use std::sync::{Arc, Mutex};
use tokio::task;
// A structure to hold information about the async tasks
struct AsyncTask {
callback: v8::Global<v8::Function>,
url: String,
}
// A global state to store pending async tasks
lazy_static::lazy_static! {
static ref PENDING_TASKS: Arc<Mutex<Vec<AsyncTask>>> = Arc::new(Mutex::new(Vec::new()));
}
// Function to register an async task
fn register_async_task(scope: &mut v8::HandleScope, task: AsyncTask) {
PENDING_TASKS.lock().unwrap().push(task);
}
// Function to poll and complete async tasks
async fn poll_async_tasks() {
loop {
// Clone the list of tasks to avoid locking for too long
let tasks = PENDING_TASKS.lock().unwrap().clone();
for task in tasks {
let url = task.url.clone();
let callback = task.callback.clone();
// Spawn an async task for each async operation
tokio::spawn(async move {
// Perform the async operation (e.g., HTTP request)
let response = reqwest::get(&url).await.unwrap(); // Handle errors appropriately
// Execute the callback in the V8 context with the response
let scope = &mut v8::HandleScope::current();
let undefined = v8::undefined(scope).into();
callback.call(scope, undefined, &[response.text().unwrap().into()]);
});
}
// Clear completed tasks from the global state
PENDING_TASKS.lock().unwrap().clear();
// Sleep for a while before checking again
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
// Function to initialize the asynchronous task polling loop
fn init_async_task_polling() {
tokio::spawn(poll_async_tasks());
}
// Your existing function
fn message_from_worker(scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, _ret: v8::ReturnValue) {
// ...
let url = /* extract URL from message */;
let callback = /* extract callback from message */;
// Register the async task
register_async_task(scope, AsyncTask { callback, url: url.clone() });
// Continue with other tasks or return
}
此示例使用
tokio::spawn
函数为每个异步操作生成异步任务。 poll_async_tasks
函数负责定期检查这些任务的状态。请注意,这只是一个基本示例,您可能需要根据您的具体要求和错误处理策略进行调整。
确保在应用程序启动时通过调用
init_async_task_polling
初始化异步任务轮询循环。此外,在异步任务中适当处理错误,并根据应用程序的需求调整轮询循环中的睡眠持续时间。
请记住将必要的依赖项添加到您的“Cargo.toml”中:
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = "0.11"
rusty_v8 = "0.12"
lazy_static = "1.4"
这种方法允许您在不阻塞 V8 运行时的情况下执行异步操作,并支持并行执行多个异步任务。