线程安全函数
在 Node.js 中,ThreadSafe Function
(opens in a new tab) 是一个复杂的概念,
众所周知,Node.js 是单线程的,所以你不能在另一个线程上访问 napi_env
(opens in a new tab)、
napi_value
(opens in a new tab) 和 napi_ref
(opens in a new tab)。
napi_env
(opens in a new tab)、
napi_value
(opens in a new tab) 和
napi_ref
(opens in a new tab) 是 Node-API
中的底层概念, NAPI-RS 的 #[napi]
宏正是在其之上构建的, NAPI-RS
也提供了一个底层 API来访问原始的 Node-API
。
Node-API
提供了复杂的 Threadsafe Function
API 来在其他线程上调用 JavaScript 函数,这个 API 非常复杂,
导致很多开发者不知道如何正确使用它,NAPI-RS 提供了一个限制版本的 Threadsafe Function
API 来简化使用:
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export function callThreadsafeFunction(callback: (...args: any[]) => any): void
ThreadsafeFunction
非常复杂,因此 NAPI-RS 生成的 TypeScript 定义并不精确,如果你想要更好的 TypeScript 类型,
你可以使用 #[napi(ts_args_type)]
来覆盖 JsFunction
参数的类型:
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi(ts_args_type = "callback: (err: null | Error, result: number) => void")]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export function callThreadsafeFunction(
callback: (err: null | Error, result: number) => void,
): void
ErrorStrategy
Threadsafe Function
有两种不同的错误处理策略,你可以在 ThreadsafeFunction
的第二个泛型参数中定义策略:
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = ...
在泛型参数中的第一个参数是 Threadsafe Function
的返回类型。
ErrorStrategy::CalleeHandled
Rust 代码中的 Err
将被传递到 JavaScript 回调的第一个参数中,
这种行为遵循了 Node.js 中的异步回调约定:
https://nodejs.org/en/learn/asynchronous-work/javascript-asynchronous-programming-and-callbacks#handling-errors-in-callbacks (opens in a new tab) ,
Node.js 中的许多异步 API 都是按照这种形式设计的,比如 fs.read
。
使用 ErrorStrategy::CalleeHandled
,你必须使用 Result
类型调用 ThreadsafeFunction
,
这样 Error
才会被处理并传递回 JavaScript 回调:
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi(ts_args_type = "callback: (err: null | Error, result: number) => void")]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::CalleeHandled> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}
ErrorStrategy::Fatal
不传递 Error
给 JavaScript 端,如果你的代码永远不会返回 Err
,你可以使用这种策略来避免在 Rust 代码中使用 Ok
封装。
通过这种策略,ThreadsafeFunction
不需要使用 Result<T>
调用,并且JavaScript 回调的第一个参数是 Rust 中的值,而不是 Error | null
。
use std::thread;
use napi::{
bindgen_prelude::*,
threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode},
};
#[napi(ts_args_type = "callback: (result: number) => void")]
pub fn call_threadsafe_function(callback: JsFunction) -> Result<()> {
let tsfn: ThreadsafeFunction<u32, ErrorStrategy::Fatal> = callback
.create_threadsafe_function(0, |ctx| {
ctx.env.create_uint32(ctx.value + 1).map(|v| vec![v])
})?;
for n in 0..100 {
let tsfn = tsfn.clone();
thread::spawn(move || {
tsfn.call(n, ThreadsafeFunctionCallMode::Blocking);
});
}
Ok(())
}
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️
export function callThreadsafeFunction(callback: (result: number) => void): void