前提:
napi_call_threadsafe_function
一般是在处理回调事件时才使用,在 native
中将回调事件传递给 JS
中,由 JS
根据这些回调事件进行后续的动作(音频的播放、暂停,UI 的显示场景等等)
实现:
需要最关键的两个东西:env
和 tsfn
env
env
顾名思义,就是 JS
的运行环境,需要它的原因是在 napi_call_function
时需要该参数来调用 js 函数tsfn
是线程安全函数,它的内部维护一个线程安全的队列,这样能确保数据按顺序传递,防止数据竞争和资源冲突,见:napi_create_async_work 和 napi_create_threadsafe_function 的使用场景分析
部分代码示例
结构体定义
typedef struct {
std::function<void(napi_env)> cb_;
bool called_;
std::mutex lock_;
std::condition_variable cv_;
} CallJsContext;
- 创建 tsfn
// Start
bool AppExecutor::Start(napi_env env) {
std::unique_lock<std::mutex> lock(lock_);
napi_status status;
if (started_) {
napi_throw_error(env, nullptr, "AppExecutor already started");
return false;
}
napi_value work_name;
// Create a string to describe this asynchronous operation.
DCHECK(napi_create_string_utf8(env, "AppExecutor AsyncWork", NAPI_AUTO_LENGTH, &work_name));
// Convert the callback retrieved from JavaScript into a thread-safe function
// which we can call from a worker thread.
DCHECK(napi_create_threadsafe_function(
env, nullptr, nullptr, work_name, 0, 1, nullptr, nullptr, this, App::CallJs, &(tsfn_)));
started_ = true;
return true;
}
- 定义
CallJs
,该函数用于传递env
参数并执行lambda
,lambda
实际是由 JS 线程处理
// This function is responsible for converting data coming in from the worker
// thread to napi_value items that can be passed into JavaScript, and for
// calling the JavaScript function.
void AppEngnExecutor::CallJs(napi_env env, napi_value js_cb, void * context, void * data) {
CallJsContext * ctx = (CallJsContext *)data;
ctx->cb_(env);
{
std::lock_guard<std::mutex> lock(ctx->lock_);
ctx->called_ = true;
}
ctx->cv_.notify_one();
}
- 定义
CallInJs
,该函数的作用是把lambda
封装到CallJsContext
,然后调用napi_call_threadsafe_function(tsfn_, ctx, napi_tsfn_blocking)
这会把ctx
投递到JS
线程的任务队列
void AppExecutor::CallInJs(std::function<void(napi_env)> cb) {
napi_status status;
CallJsContext * ctx = new CallJsContext;
ctx->cb_ = cb;
ctx->called_ = false;
// Initiate the call into JavaScript. The call into JavaScript will not
// have happened when this function returns, but it will be queued.
DCHECK(napi_call_threadsafe_function(tsfn_, ctx, napi_tsfn_blocking));
{
std::unique_lock<std::mutex> lock(ctx->lock_);
while (!ctx->called_)
ctx->cv_.wait(lock);
}
delete ctx;
}
- 定义 native 的回调函数
int AppExecutor::NotifyMsg(void * pUser, int nMsgID, int nV1, int nV2, int nV3, void * pData) {
AppExecutor* executor = (AppExecutor*)pUser;
```
executor->CallInJs([&](napi_env env) {
napi_status status;
const size_t argc = 5;
napi_value argv[argc] = {0};
DCHECK(napi_create_int32(env, nMsgID, &argv[0]));
DCHECK(napi_create_int32(env, nV1, &argv[1]));
DCHECK(napi_create_int32(env, nV2, &argv[2]));
DCHECK(napi_create_int32(env, nV3, &argv[3]));
```
if (executor->cb_ref_ != nullptr) {
napi_status status;
napi_value jsthis;
// 获取 jsthis
DCHECK(napi_get_reference_value(env, executor->app_->GetRef(), &jsthis));
napi_value value;
// 获取 js 的回调函数引用
DCHECK(napi_get_reference_value(env, executor->cb_ref_, &value));
DCHECK(napi_call_function(env, jsthis, value, argc, argv, nullptr));
}
});
return 0;
}
具体解析:
CallInJs
先把lambda
封装到CallJsContext
,然后调用napi_call_threadsafe_function(tsfn_, ctx, napi_tsfn_blocking)
。- 这会把
ctx
投递到JS
线程的任务队列。 - 当
JS
线程空闲时,N-API
会调用你注册的CallJs
静态方法,并传入napi_env
和ctx
。 CallJs
里会执行ctx->cb_(env)
;,也就是你传入的lambda
。- 所以,
lambda
的实际执行时机是JS
线程处理到这个任务时,而不是在调用CallInJs
的线程里。
这段解析的小结就是 CallInJs
里的 lambda
是异步投递到 JS
线程,由 N-API
框架在合适时机(通常很快)回调执行的。
流程分析
触发 native 回调函数 => CallInJs 将 lambda 封装到 CallJsContext 并将 ctx 投递到 JS 线程的任务队列 => 在 JS 线程中,由 N-API 调用 CallJs,并传入有效的 napi_env,执行 lambda,最终调用 Js 的回调函数 => Js 的回调函数被触发,在 JS 中处理这些事件(音频的播放、暂停,UI 的显示场景等等)
销毁 ref 和释放 tsfn
void AppEngnExecutor::Shutdown(napi_env env) {
std::unique_lock<std::mutex> lock(lock_);
if (started_) {
WorkComplete();
}
if (cb_ref_ != nullptr) {
napi_status status;
DCHECK(napi_delete_reference(env, cb_ref_));
cb_ref_ = nullptr;
}
}
// This function runs on the main thread after `ExecuteWork` exits.
void AppEngnExecutor::WorkComplete() {
napi_status status;
// Clean up the thread-safe function.
DCHECK(napi_release_threadsafe_function(tsfn_, napi_tsfn_release));
// Set both values to nullptr so JavaScript can order a new run of the thread.
tsfn_ = nullptr;
started_ = false;
}