napi_call_threadsafe_function 实现跨线程获取数据

前提:

napi_call_threadsafe_function 一般是在处理回调事件时才使用,在 native 中将回调事件传递给 JS 中,由 JS 根据这些回调事件进行后续的动作(音频的播放、暂停,UI 的显示场景等等)

实现:

需要最关键的两个东西:envtsfn

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;
  1. 创建 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;
}
  1. 定义 CallJs,该函数用于传递 env 参数并执行 lambdalambda 实际是由 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();
}
  1. 定义 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;
}
  1. 定义 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;
}

具体解析:

  1. CallInJs 先把 lambda 封装到 CallJsContext,然后调用 napi_call_threadsafe_function(tsfn_, ctx, napi_tsfn_blocking)
  2. 这会把 ctx 投递到 JS 线程的任务队列。
  3. JS 线程空闲时,N-API 会调用你注册的 CallJs 静态方法,并传入 napi_envctx
  4. CallJs 里会执行 ctx->cb_(env);,也就是你传入的 lambda
  5. 所以,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;
}
<think>好的,我现在需要回答用户关于鸿蒙操作系统中子线程的使用方法和实现机制的问题。首先,我得仔细阅读用户提供的参考资料,也就是引用[1]到[4],确保理解里面的内容。用户特别提到了C++子线程与UI主线程的通信方案,所以重点应该放在线程间通信上,尤其是如何在Native子线程和ArkTS主线程之间安全地传递消息。 首先,我需要整理一下参考资料中的关键点。引用[2]和[3]提到了使用Node-API中的napi_threadsafe_function实现线程安全函数,允许子线程与主线程异步通信。引用[3]和[4]详细描述了实现流程,包括创建线程安全函数、使用异步工作(napi_create_async_work)、处理业务逻辑和资源清理等步骤。这些都是需要涵盖的重点内容。 接下来,我需要按照用户的要求,结构清晰地分步骤解释鸿蒙子线程的用法和实现机制。可能需要分为几个部分:线程基础概念、子线程创建、通信机制、实现流程、注意事项等。同时,用户要求回答中使用中文,并且数学表达式用$...$格式,独立公式用$$...$$,不过在这个问题中可能数学公式较少,但需要注意代码块的格式是否正确。 然后,我需要确保回答中的技术细节准确无误,例如正确引用napi_threadsafe_function和相关API的使用步骤。同时,根据用户提供的示例,可能需要生成相关问题,所以得在最后添加几个相关问题,帮助用户进一步学习。 需要注意的是,用户引用的资料中有关于Node-API的使用,特别是线程安全函数和异步任务的实现流程。这部分需要详细解释,包括如何注册回调、处理异步任务以及资源回收。同时,要强调ArkTS主线程不能直接被子线程访问,必须通过安全机制进行通信。 在组织内容时,应该先介绍子线程的基本概念和必要性,然后说明在鸿蒙中如何创建子线程,接着详细讲解通信机制,包括使用的API和具体步骤。最后提到注意事项,比如避免阻塞主线程、资源管理等。 需要检查是否有遗漏的关键点,比如napi_create_async_work的作用,ExecuteWork和WorkComplete的具体功能,以及如何通过callback回到主线程调用ArkTS函数。这些都是用户资料中的重点,必须涵盖到。 最后,生成的相关问题需要围绕线程管理、通信优化、与其他系统的对比等方向,帮助用户深入理解。例如,如何确保线程安全、优化通信性能、处理线程间数据共享等。</think>### 鸿蒙操作系统中子线程的使用方法与实现机制 #### 1. 子线程基础概念 在鸿蒙(HarmonyOS)中,UI主线程负责处理用户交互和界面渲染,而子线程用于执行耗时操作(如网络请求、文件读写、复杂计算等),避免阻塞主线程[^2]。**子线程不能直接操作UI资源**,需通过线程安全机制与主线程通信。 --- #### 2. 子线程的创建与使用 **C++层子线程创建示例**: ```cpp #include <thread> void NativeThreadTask() { // 子线程中的业务逻辑 } // 创建并启动子线程 std::thread worker(NativeThreadTask); worker.detach(); // 分离线程,避免资源泄漏 ``` - **注意**:需在`CMakeLists.txt`中添加`libc++_shared.so`依赖以支持C++多线程。 --- #### 3. 子线程与主线程通信机制 鸿蒙通过**Node-API的线程安全函数(napi_threadsafe_function)**实现跨线程通信,核心流程如下: ##### 3.1 核心API与流程 1. **创建线程安全函数** ```cpp napi_create_threadsafe_function( env, func, // ArkTS回调函数 async_resource, async_resource_name, max_queue_size, initial_thread_count, finalize_cb, context, &threadsafe_func ); ``` 2. **注册异步任务** 使用`napi_create_async_work`定义异步任务的执行和完成回调: ```cpp napi_create_async_work( env, nullptr, resource_name, ExecuteWork, // 子线程中执行 WorkComplete, // 主线程中完成清理 &async_data, &async_work ); ``` 3. **通信过程** - **子线程处理**:在`ExecuteWork`中执行业务逻辑,通过`napi_call_threadsafe_function`触发线程安全函数。 - **主线程回调**:线程安全函数将任务封装后,通过`napi_call_function`调用ArkTS侧注册的callback[^3]。 --- #### 4. 实现流程详解 1. **ArkTS侧定义异步任务** ```typescript nativeModule.runAsyncTask((result: string) => { console.log("Result from C++ thread: " + result); }); ``` 2. **Native侧绑定线程安全函数** - 将ArkTS的`callback`绑定到线程安全函数。 - 通过`napi_acquire_threadsafe_function`获取函数句柄。 3. **子线程触发通信** ```cpp napi_call_threadsafe_function( threadsafe_func, data, napi_tsfn_blocking ); ``` 4. **资源清理** 在`WorkComplete`中使用`napi_release_threadsafe_function`释放资源[^4]。 --- #### 5. 注意事项 1. **线程安全**:避免直接操作主线程资源,所有跨线程数据需通过安全接口传递。 2. **性能优化**:控制子线程数量,避免过度竞争;使用`non-blocking`模式减少延迟。 3. **生命周期管理**:确保子线程在组件销毁时终止,防止内存泄漏。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值