【关键字】
Node-API / 线程安全开发 / napi_call_threadsafe_function / 跨线程获取数据
【问题描述】
项目中需要对接一个C++库,这个库的所有处理逻辑都在它的子线程里,并不在NAPI的主线程,因此涉及到跨线程的问题。假定主线程为线程A,三方库子线程为线程B。
1、调用napi_create_threadsafe_function方法,注入线程安全的方法callProxyFunction。该方法按照nodejs文档要求的方法签名,定义为static void callProxyFunction(napi_env env, napi_value js_callback, void *context, void *data)。其中第四个参数*data就是给调用该函数的地方植入自定义数据。
2、将第一步创建的callProxyFunction方法绑定到napi_threadsafe_function callProxySafeThreadHandle上。
3、在B线程中调用napi_call_threadsafe_function(callProxySafeThreadHandle, params, napi_tsfn_nonblocking),其中params参数就是植入自定义数据的地方。
4、A线程的callProxyFunction方法被napi主动调用。此时第四个参数*data,按照预想逻辑,应该可以获取第3步的params参数。实际上,经过调试地址确实是一样的,但是数据全部为空,似乎有跨线程问题。
因此想要咨询下,框架有没有提供什么方法可以获取第3步里传递的用户数据?
【解决方案】
经验证,可以跨线程获取数据,以下为示例代码。
-
hello.cpp:
#include "napi/native_api.h" #define LOG_TAG "testTag" #include <hilog/log.h> #include <thread> // 指向napi_value js_cb napi_ref cbObj = nullptr; // 线程安全函数 napi_threadsafe_function tsfn; // Native侧Value值 static int cValue = 999; typedef struct TestData { int data; int type; }TestData; // 回调 static void CallJs(napi_env env, napi_value js_cb, void *context, void *data) { TestData testDatal = *(TestData *)data; OH_LOG_INFO(LOG_APP, "CallJs, data=%{public}d, type:%{public}d", testDatal.data, testDatal.type); // 获取引用值 napi_get_reference_value(env, cbObj, &js_cb); // 创建一个ArkTS number作为ArkTS function的入参。 napi_value argv; napi_create_int32(env, cValue, &argv); napi_value result = nullptr; napi_call_function(env, nullptr, js_cb, 1, &argv, &result); napi_get_value_int32(env, result, &cValue); } // Native 主线程 static napi_value ThreadsTest(napi_env env, napi_callback_info info) { OH_LOG_INFO(LOG_APP, "dwt CallJs 0"); // 从ArkTS侧获取的参数的数量 size_t argc = 1; napi_value js_cb, work_name; // 获取ArkTS参数 napi_get_cb_info(env, info, &argc, &js_cb, nullptr, nullptr); // 指向napi_value js_cb 的 napi_ref cbObj napi_create_reference(env, js_cb, 1, &cbObj); // 通过UTF8编码的C字符串数据创建work_name napi_create_string_utf8(env, "Work Item", NAPI_AUTO_LENGTH, &work_name); // 创建线程安全函数 napi_create_threadsafe_function(env, js_cb, NULL, work_name, 0, 1, NULL, NULL, NULL, CallJs, &tsfn); // 其他线程中调用线程安全函数 std::thread t([]() { // 可获取线程ID std::thread::id this_id = std::this_thread::get_id(); napi_acquire_threadsafe_function(tsfn); TestData testData; testData.data = 3; testData.type = 4; napi_call_threadsafe_function(tsfn, &testData, napi_tsfn_blocking); }); t.detach(); return NULL; }
- index.d.ts:
export const threadsTest: (func: object) => number;
-
entry module Index.ets 调用:
import entry from 'libentry.so'; import hilog from '@ohos.hilog'; @Entry @Component struct Index { @State value: number = 20; @State cnt: number = 10; build() { Row() { Column() { Button("跨线程调用JS函数") .onClick(() => { entry.threadsTest((value:number) => { value += 10 this.cnt += 20 hilog.info(0x0000, 'testTag', 'js callback value = ' + value + " , cnt = " + this.cnt); return value }) }).margin(10) } .width('100%') } .height('100%') } }
示例代码的实现逻辑说明:
hello.cpp文件示例代码中,如下为线程B(TestData结构体的数据,就是第三步传递的自定义数据):
// 其他线程中调用线程安全函数
std::thread t([]() {
// 可获取线程ID
std::thread::id this_id = std::this_thread::get_id();
napi_acquire_threadsafe_function(tsfn);
TestData testData;
testData.data = 3;
testData.type = 4;
napi_call_threadsafe_function(tsfn, &testData, napi_tsfn_blocking);
});t.detach();
A线程的callProxyFunction方法即为如下回调函数(第四个参数需要强转后获取数据,TestData testDatal = *(TestData *)data;):
static void CallJs(napi_env env, napi_value js_cb, void *context, void *data) {
TestData testDatal = *(TestData *)data;
OH_LOG_INFO(LOG_APP, "CallJs, data=%{public}d, type:%{public}d", testDatal.data, testDatal.type);
...
运行后,执行结果如下:
01-25 14:23:00.801 31680-31680 A00000/testTag pid-31680 I Succeeded in loading the content. Data:
01-25 14:23:12.550 31680-31680 A00000/testTag com.examp...lication I dwt CallJs 0
01-25 14:23:12.551 31680-31680 A00000/testTag com.examp...lication I CallJs, data=3, type:4