文档
概念
线程安全函数

线程安全函数

在 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 来简化使用:

lib.rs
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(())
}

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️

index.d.ts
export function callThreadsafeFunction(callback: (...args: any[]) => any): void

ThreadsafeFunction 非常复杂,因此 NAPI-RS 生成的 TypeScript 定义并不精确,如果你想要更好的 TypeScript 类型, 你可以使用 #[napi(ts_args_type)] 来覆盖 JsFunction 参数的类型:

lib.rs
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(())
}

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️

index.d.ts
export function callThreadsafeFunction(
  callback: (err: null | Error, result: number) => void,
): void

ErrorStrategy

Threadsafe Function 有两种不同的错误处理策略,你可以在 ThreadsafeFunction 的第二个泛型参数中定义策略:

lib.rs
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 回调:

lib.rs
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

lib.rs
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(())
}

⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️

index.d.ts
export function callThreadsafeFunction(callback: (result: number) => void): void