【HarmonyOS NEXT】如何通过NAPI napi_call_threadsafe_function接口实现跨线程获取数据

文章讨论了如何在Node.js环境中通过napi_create_threadsafe_function实现线程安全地调用C++库,解决跨线程数据传递问题。作者分享了一个示例代码,展示了如何正确设置和调用线程安全函数以获取跨线程传递的用户数据。
摘要由CSDN通过智能技术生成

 

【关键字】

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
1. napi_create_int32: 用于创建一个 int32 类型的 JavaScript 数值。函数原型为:napi_status napi_create_int32(napi_env env, int32_t value, napi_value *result)。其中,env 表示当前的 napi 环境,value 表示要创建的 int32 类型的数值,result 用于存储创建的 JavaScript 数值。 2. napi_get_reference_value: 用于从一个 napi 引用中获取对应的 JavaScript 对象。函数原型为:napi_status napi_get_reference_value(napi_env env, napi_ref ref, napi_value *result)。其中,env 表示当前的 napi 环境,ref 表示要获取的引用对象,result 用于存储获取到的 JavaScript 对象。 3. napi_call_function: 用于调用一个 JavaScript 函数。函数原型为:napi_status napi_call_function(napi_env env, napi_value recv, napi_value func, size_t argc, const napi_value *argv, napi_value *result)。其中,env 表示当前的 napi 环境,recv 表示函数的接收者,func 表示要调用的 JavaScript 函数,argc 表示参数个数,argv 表示参数列表,result 用于存储函数调用的结果。 4. NAPI_CALL_BASE: 用于检查 napi 方法返回的错误码,如果出现错误则将错误信息打印到日志中。函数原型为:#define NAPI_CALL_BASE(env, call) \ do { \ napi_status status = (call); \ if (status != napi_ok) { \ const napi_extended_error_info* error_info = 0; \ napi_get_last_error_info((env), &error_info); \ const char* error_message = (error_info->error_message != NULL) ? \ error_info->error_message : "empty error message"; \ printf("NAPI_CALL_BASE failed at %s:%d status=%d, error_message=%s\n", \ __FILE__, __LINE__, status, error_message); \ } \ } while(0) 5. AsyncTask::Schedule: 用于将一个异步任务添加到事件循环中执行。函数原型为:napi_status AsyncTask::Schedule(napi_env env, AsyncTaskExecuteCallback execute, AsyncTaskCompleteCallback complete, void *data, napi_async_context *context)。其中,env 表示当前的 napi 环境,execute 表示异步任务的执行函数,complete 表示异步任务执行完成后的回调函数,data 表示异步任务的数据,context 表示异步任务的上下文。 使用注意: 1. napi_create_int32 和 napi_get_reference_value 的返回值应该被检查,以确保正确地创建和获取 JavaScript 对象。 2. 在调用 napi_call_function 之前,需要确保传入的参数正确,包括函数接收者、函数本身和参数列表。 3. 需要注意异步任务的执行时间,以免阻塞主线程。 4. 在使用 NAPI_CALL_BASE 宏时,需要注意错误信息的输出方式,可以根据需要进行修改。 5. 在使用 AsyncTask::Schedule 时,需要注意异步任务的数据,以确保异步任务可以正确地访问和修改数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值