在鸿蒙NDK开发中,“非JS线程调用JS函数”是个高频坑——直接调用容易触发线程竞争,导致应用崩溃、数据错乱;但多线程场景(如后台计算、异步IO)又绕不开跨线程通信。
Node-API提供的napi_create_threadsafe_function
接口,正是为解决这个痛点而生:它能让非JS线程安全地触发JS函数,自动处理线程同步与数据传递。本文通过场景解析+完整可运行代码+步骤拆解,带你从“踩坑”到“精通”,彻底搞定鸿蒙多线程JS交互。
一、先搞懂:napi_create_threadsafe_function到底能解决什么问题?
napi_create_threadsafe_function
的核心是创建一个线程安全的JS函数代理——允许从任意非JS线程(如libuv工作线程、自定义线程)调用JS函数,无需手动处理线程锁或同步逻辑,从根源避免3类问题:
- 线程竞争:多线程同时操作JS对象导致的内存错乱;
- 死锁:手动加锁不当引发的线程阻塞;
- 主线程阻塞:在JS主线程外安全执行回调,不影响应用流畅性。
二、3个必用场景:什么时候该用它?
不是所有多线程场景都需要它,以下3类场景是它的“主战场”,用对能显著减少调试成本:
场景类型 | 业务示例 | 核心价值 |
---|---|---|
1. 异步计算回调 | 后台线程执行AI图像识别,完成后通知JS更新UI | 避免阻塞JS主线程,同时安全传递计算结果 |
2. 跨线程数据同步 | 多个工作线程读写全局配置,通过JS函数统一处理 | 用JS函数封装数据操作,确保多线程读写安全 |
3. 多线程任务调度 | 生产者线程生成任务,通过JS函数通知消费者线程 | 以JS函数为“信使”,实现线程间安全通信 |
三、实操:从0到1实现线程安全调用(完整代码+步骤拆解)
下面用一个“后台线程执行任务→调用JS函数获取结果→回收资源”的完整案例,带你掌握napi_create_threadsafe_function
的全流程使用,代码可直接复用。
前置准备:定义上下文结构体
首先定义CallbackData
结构体,用于存储线程安全函数句柄、异步任务句柄,方便跨函数传递资源:
#include "napi/native_api.h"
#include "hilog/log.h"
#include <future> // 用于线程间结果同步(非必须,按需使用)
// 上下文结构体:管理线程安全函数与异步任务资源
struct CallbackData {
napi_threadsafe_function tsfn; // 线程安全函数句柄
napi_async_work work; // 异步任务句柄(模拟非JS线程环境)
};
步骤1:创建线程安全函数(Native入口)
在Native接口StartThread
中,接收JS侧传入的回调函数,调用napi_create_threadsafe_function
创建安全代理,并关联异步任务(用napi_create_async_work
模拟非JS线程):
// Native对外接口:JS侧通过startThread调用,传入JS回调函数
static napi_value StartThread(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value jsCb = nullptr; // 接收JS侧传入的回调函数
CallbackData* callbackData = new CallbackData(); // 动态分配,后续需手动释放
// 1. 从JS调用参数中提取回调函数
napi_get_cb_info(env, info, &argc, &jsCb, nullptr, nullptr);
// 2. 创建线程安全函数(核心步骤)
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "ThreadSafeFuncDemo", NAPI_AUTO_LENGTH, &resourceName);
napi_create_threadsafe_function(
env, // JS环境(仅在创建和释放时使用)
jsCb, // JS侧传入的原始回调函数
nullptr, // 可选:异步资源对象(一般传nullptr)
resourceName, // 资源名称(调试时区分不同线程安全函数)
0, // 最大队列长度(0=无限制,回调会排队执行)
1, // 线程池线程数(1=单线程处理回调)
nullptr, // 可选:线程退出回调(资源清理用)
nullptr, // 可选:上下文数据(全局共享数据)
CallJs, // 关键:实际触发JS回调的函数(见步骤3)
&callbackData->tsfn // 输出:线程安全函数句柄(后续调用和释放用)
);
// 3. 创建异步任务(模拟非JS线程环境,实际项目可替换为自定义线程)
napi_create_async_work(
env,
nullptr, // 可选:异步资源对象
resourceName, // 资源名称(与线程安全函数一致,方便调试)
ExecuteWork, // 非JS线程执行的任务(见步骤2)
WorkComplete