线程安全函数主要用于在多个线程之间共享和调用,而不会出现竞争条件或死锁。例如以下场景:
其中,因为线程安全函数被创建时初始引用计数是1,如果没有使用napi_acquire_threadsafe_function增加引用计数,不会被系统主动回收。
同时,napi_env不能在不同线程之间共享或传递,缓存napi_env并在不同线程中使用,会导致线程安全问题,因此不建议缓存env在非主线程中使用,参考:napi_env禁止缓存的原因是什么。
参数名 | 参数位置 | 参数说明 |
|---|---|---|
func | 2 | ArkTS应用侧传入的回调接口callback,可为空。当该值为nullptr时,下文call_js_cb不能为nullptr。反之,亦然。两者不可同时为空。 |
call_js_cb | 10 | 子线程需要处理的线程安全回调任务,类似于异步工作项中的complete回调。当调用napi_call_threadsafe_function后,被抛到ArkTS主线程EventLoop中,等待调度执行。当该值为空时,系统将会调用默认回调接口。 |
若在ArkTS侧定义了回调方法,可以传入第二个参数进行调用。
推荐做法:初始化时创建一个全局线程安全函数并持有,整个生命周期内复用,业务退出时统一释放。每次调用使用napi_acquire_threadsafe_function和napi_release_threadsafe_function管理引用计数即可。
C++侧示例代码:
#include "napi/native_api.h"
#include <thread>
#include <atomic>
static napi_threadsafe_function g_tsfn = nullptr;
static std::atomic<bool> g_running(true);
static void CallJs(napi_env env, napi_value jsCb, void *context, void *data)
{
if (env == nullptr || !g_running.load()) {
return;
}
napi_handle_scope scope = nullptr;
napi_open_handle_scope(env, &scope);
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
char *message = static_cast<char *>(data);
napi_value jsStr = nullptr;
napi_create_string_utf8(env, message, NAPI_AUTO_LENGTH, &jsStr);
napi_value result = nullptr;
napi_call_function(env, undefined, jsCb, 1, &jsStr, &result);
napi_close_handle_scope(env, scope);
free(message);
}
// 初始化:ArkTS主线程调用一次,创建并持有线程安全函数
static napi_value InitTsfn(napi_env env, napi_callback_info info)
{
size_t argc = 1;
napi_value jsCb = nullptr;
napi_get_cb_info(env, info, &argc, &jsCb, nullptr, nullptr);
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "GlobalTsfn", NAPI_AUTO_LENGTH, &resourceName);
napi_create_threadsafe_function(env, jsCb, nullptr, resourceName,
0, 1, nullptr, nullptr, nullptr, CallJs, &g_tsfn);
return nullptr;
}
// 子线程高频调用:直接call,不反复create/release
void WorkerLoop()
{
int counter = 0;
while (g_running.load()) {
counter++;
std::string msg = "result_" + std::to_string(counter);
char *data = strdup(msg.c_str());
napi_status status = napi_call_threadsafe_function(
g_tsfn, data, napi_tsfn_nonblocking);
if (status != napi_ok) {
free(data);
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
}
// 启动工作线程
static napi_value StartWorker(napi_env env, napi_callback_info info)
{
napi_acquire_threadsafe_function(g_tsfn);
std::thread worker(WorkerLoop);
worker.detach();
return nullptr;
}
// 销毁:业务结束时调用
static napi_value DestroyTsfn(napi_env env, napi_callback_info info)
{
g_running = false;
if (g_tsfn != nullptr) {
napi_release_threadsafe_function(g_tsfn, napi_tsfn_abort);
g_tsfn = nullptr;
}
return nullptr;
} ArkTS侧调用示例:
import nativeModule from 'libentry.so';
nativeModule.initTsfn((msg: string) => {
console.info(`主线程收到: ${msg}`);
});
nativeModule.startWorker();
// 业务结束时调用 子线程非阻塞通知主线程的代码示例:
// 子线程中非阻塞通知主线程
napi_status status = napi_call_threadsafe_function(
tsfn, &result, napi_tsfn_nonblocking);
if (status == napi_queue_full) {
// 丢弃或缓存,绝不阻塞等待
} 对于需要跨多次Node-API调用保持引用的JS对象,在主线程中使用napi_create_reference创建持久引用,使用napi_get_reference_value获取引用值,不再需要时调用napi_delete_reference释放。napi_ref解决的是跨调用生命周期问题(防止GC回收),而非跨线程问题,同样只能在同一isolate(主线程)中使用。
持久引用管理代码示例:
// 持久引用管理(主线程) napi_ref callbackRef; napi_create_reference(env, jsCallback, 1, &callbackRef); // 后续获取引用值 napi_value jsCb; napi_get_reference_value(env, callbackRef, &jsCb); // 不再需要时释放 napi_delete_reference(env, callbackRef);
当C++异步线程需要回调ArkTS函数时,需通过napi_ref持有回调对象、通过napi_create_threadsafe_function实现跨线程安全调用,并在页面销毁时正确清理引用,才能避免野指针崩溃。
napi_value是局部句柄,仅在当前HandleScope内有效,作用域结束或GC触发后即失效。将napi_value直接存入C++全局变量或异步上下文,后续访问时对象可能已被回收。napi_env与线程1:1绑定,禁止缓存napi_env到全局变量并在其他线程使用。
以下示例展示C++子线程异步执行任务后安全回调ArkTS函数,并正确处理页面销毁场景。
C++侧代码(Native模块):
#include "napi/native_api.h"
#include "hilog/log.h"
#include <thread>
#include <string>
// 上下文结构体,持有napi_ref和线程安全函数
struct AsyncContext {
napi_ref callbackRef = nullptr; // 持久引用,锁定ArkTS回调函数
napi_threadsafe_function tsfn = nullptr; // 线程安全函数,用于跨线程回调
bool isCancelled = false; // 取消标志,页面销毁时置为true
};
// 线程安全函数的JS回调,在ArkTS主线程执行
static void CallJsCallback(napi_env env, napi_value jsCallback, void* context, void* data) {
if (data == nullptr || env == nullptr) {
return;
}
AsyncContext* ctx = static_cast<AsyncContext*>(context);
// 检查是否已取消(页面已销毁)
if (ctx->isCancelled) {
return;
}
char* resultStr = static_cast<char*>(data);
napi_value jsStr;
napi_create_string_utf8(env, resultStr, NAPI_AUTO_LENGTH, &jsStr);
napi_value global;
napi_get_global(env, &global);
napi_value result;
napi_status status = napi_call_function(env, global, jsCallback, 1, &jsStr, &result);
if (status != napi_ok) {
OH_LOG_ERROR(LOG_APP, "CallJsCallback: napi_call_function failed, status=%{public}d", status);
}
free(resultStr);
}
// 线程安全函数销毁时的清理回调
static void TsfnFinalizeCallback(napi_env env, void* finalizeData, void* finalizeHint) {
AsyncContext* ctx = static_cast<AsyncContext*>(finalizeData);
if (ctx) {
if (ctx->callbackRef) {
napi_delete_reference(env, ctx->callbackRef);
ctx->callbackRef = nullptr;
}
delete ctx;
}
OH_LOG_INFO(LOG_APP, "TsfnFinalizeCallback: resources cleaned up");
}
// C++子线程执行函数
static void NativeWorker(AsyncContext* ctx) {
if (ctx == nullptr || ctx->isCancelled) {
return;
}
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(2));
// 检查取消标志:页面可能已销毁
if (ctx->isCancelled) {
OH_LOG_INFO(LOG_APP, "NativeWorker: task cancelled, skip callback");
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);
return;
}
char* resultData = strdup("Async task completed successfully");
napi_status status = napi_call_threadsafe_function(
ctx->tsfn, resultData, napi_tsfn_nonblocking);
if (status != napi_ok) {
OH_LOG_ERROR(LOG_APP, "NativeWorker: call tsfn failed, status=%{public}d", status);
free(resultData);
}
// 子线程释放对tsfn的引用
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_release);
}
// ArkTS调用入口:启动异步任务
static napi_value StartAsyncTask(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1) {
OH_LOG_ERROR(LOG_APP, "StartAsyncTask: missing callback argument");
return nullptr;
}
AsyncContext* ctx = new AsyncContext();
// 为ArkTS回调函数创建napi_ref(强引用,refcount=1)
napi_status status = napi_create_reference(env, args[0], 1, &ctx->callbackRef);
if (status != napi_ok) {
OH_LOG_ERROR(LOG_APP, "StartAsyncTask: create reference failed");
delete ctx;
return nullptr;
}
// 创建线程安全函数
napi_value resourceName;
napi_create_string_utf8(env, "AsyncTaskTSFN", NAPI_AUTO_LENGTH, &resourceName);
status = napi_create_threadsafe_function(
env, args[0], nullptr, resourceName, 0, 1, ctx,
TsfnFinalizeCallback, ctx, CallJsCallback, &ctx->tsfn);
if (status != napi_ok) {
OH_LOG_ERROR(LOG_APP, "StartAsyncTask: create tsfn failed");
napi_delete_reference(env, ctx->callbackRef);
delete ctx;
return nullptr;
}
std::thread worker(NativeWorker, ctx);
worker.detach();
return nullptr;
}
// 取消接口:页面销毁时调用,防止野指针崩溃
static napi_value CancelAsyncTask(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
AsyncContext* ctx = nullptr;
napi_get_value_external(env, args[0], reinterpret_cast<void**>(&ctx));
if (ctx) {
// 设置取消标志,子线程检测后不再回调
ctx->isCancelled = true;
// 主动释放napi_ref,解除对ArkTS回调函数的强引用
if (ctx->callbackRef) {
napi_delete_reference(env, ctx->callbackRef);
ctx->callbackRef = nullptr;
}
// 中止线程安全函数,阻止后续回调投递
if (ctx->tsfn) {
napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
ctx->tsfn = nullptr;
}
}
return nullptr;
}
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
{"startAsyncTask", nullptr, StartAsyncTask, nullptr, nullptr, nullptr, napi_default, nullptr},
{"cancelAsyncTask", nullptr, CancelAsyncTask, nullptr, nullptr, nullptr, napi_default, nullptr},
};
napi_define_properties(env, exports, sizeof(descriptors) / sizeof(descriptors[0]), descriptors);
return exports;
}
NAPI_MODULE(native_async_demo, Init) ArkTS侧代码:
import nativeModule from 'libnative_async_demo.so';
@Entry
@Component
struct AsyncDemoPage {
private isPageActive: boolean = true;
aboutToDisappear() {
this.isPageActive = false;
nativeModule.cancelAsyncTask();
}
build() {
Column() {
Button('启动异步任务')
.onClick(() => {
this.isPageActive = true;
nativeModule.startAsyncTask((result: string) => {
if (!this.isPageActive) {
console.info('页面已销毁,忽略回调');
return;
}
console.info(`异步结果: ${result}`);
});
})
}
.width('100%')
.height('100%')
}
} napi_value引用计数管理规则:
场景 | 正确做法 | 错误做法 |
|---|---|---|
临时使用 | 用napi_value,依赖HandleScope自动释放 | 无 |
存入全局/成员变量 | 用napi_create_reference创建napi_ref | 直接存napi_value |
异步回调传递 | 用napi_ref或napi_threadsafe_function | 把napi_value塞进异步上下文 |
跨线程通信 | 用napi_threadsafe_function | 直接传递napi_value或缓存napi_env |
引用计数增减 | napi_reference_ref / napi_reference_unref | 忽略计数管理 |
避免崩溃的关键要点:
napi_tsfn_release与napi_tsfn_abort的区别:
在异步回调或循环中创建大量napi_value时,应使用napi_open_handle_scope/napi_close_handle_scope及时释放临时句柄,避免短时间内积累大量对象导致内存压力:
napi_handle_scope scope; napi_open_handle_scope(env, &scope); napi_value tempObj; napi_create_object(env, &tempObj); // ... napi_close_handle_scope(env, scope);
Q:在Native接口实现中,为避免应用侧主线程阻塞,是否只能通过返回临时空值给ArkTS应用侧来实现?
A:不是。为避免阻塞ArkTS应用侧主线程,需通过napi_create_promise创建并返回Promise对象,确保主线程可继续执行其他任务。参考使用Node-API接口处理异步操作。
Q:线程安全函数的执行流程是怎样的?C++子线程如何将异步处理结果反馈到ArkTS应用侧?
A:C++子线程执行异步业务逻辑并将处理结果写入上下文数据,然后调用napi_call_threadsafe_function将线程安全回调napi_threadsafe_function_call_js抛给EventLoop事件循环排队执行。在线程安全回调执行时,callback模式通过napi_call_function调起ArkTS侧传入的回调,Promise模式通过napi_resolve_deferred将结果resolve回ArkTS应用侧。参考使用Node-API接口进行线程安全开发。