HarmonyOS鸿蒙Next NAPI异步调用入门

HarmonyOS鸿蒙Next NAPI异步调用入门

HarmonyOS Next NAPI异步调用入门

为什么需要异步调用?

TS是单线程,如果TS调用C++方法执行耗时任务,比如文件操作、网络请求、数据库操作、图像处理等需要在C++层创建线程来异步执行。如果需要获取异步任务返回结果一般通过回调方式获取。如果要维护原生线程和主线程之间同步需要一些工作量,NAPI基于异步 I/O 库libuv提供了异步调用机制,统一管理线程等,简化开发者使用。

NAPI异步调用介绍

HarmonyOS Next基于OpenHarmony Napi 标准系统异步接口实现支持Callback方式和Promise方式。

异步调用API介绍

  • 创建异步work:napi_create_async_work()
napi_status napi_create_async_work(
    napi_env env,
    napi_value async_resource,
    napi_value async_resource_name,
    napi_async_execute_callback execute,
    napi_async_complete_callback complete,
    void* data,
    napi_async_work* result);

参数说明:

  • 【in】env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。

  • 【in】async_resource: 可选项,关联async_hooks。

  • 【in】async_resource_name: 异步资源标识符,主要用于async_hooks API暴露断言诊断信息。

  • 【in】execute: 执行业务逻辑计算函数,由worker线程池调度执行。在该函数中执行IO、CPU密集型任务,不阻塞主线程。

  • 【in】complete: execute参数指定的函数执行完成或取消后,触发执行该函数。此函数在EventLoop线程中执行。

  • 【in】data: 用户提供的上下文数据,用于传递数据。

  • 【out】result: napi_async_work*指针,用于返回当前此处函数调用创建的异步工作项。返回值:返回napi_ok表示转换成功,其他值失败。

  • 将异步work加入执行队列:napi_queue_async_work()

napi_status napi_queue_async_work(
    napi_env env,
    napi_async_work work);

参数说明:

  • 【in】env: 入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。

  • 【in】work: napi_create_async_work创建的句柄

  • 取消异步work:napi_cancel_async_work

napi_status napi_cancel_async_work(
    napi_env env,
    napi_async_work work);

参数说明:

  • 【in】env: 入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
  • 【in】work: napi_create_async_work创建的句柄

如果排队的work尚未开始,则取消这个work。如果它已经开始执行,则无法取消,并将返回 napi_generic_failure。如果成功,将使用状态值 napi_cancelled 调用完成回调。即使工作已成功取消,也不应该在完成回调调用之前删除这个work。

  • 删除异步work:napi_delete_async_work
napi_status napi_delete_async_work(
    napi_env env,
    napi_async_work work);

参数说明:

  • 【in】env: 入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
  • 【in】work: napi_create_async_work创建的句柄

异步方式实现原理

同步方式中所有的代码处理都在原生方法(主线程)中完成,异步方式依赖NAPI框架提供的napi_create_async_work()函数创建异步工作项,原生方法被调用时,原生方法完成数据接收、转换,存入上下文数据,之后创建一个异步工作项,并加入调度队列,由异步工作线程池统一调度,原生方法返回空值(Callback方式)或返回Promise对象(Promise方式)。

异步工作项中定义了2个函数,一个用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在worker线程中完成业务逻辑计算(不阻塞主线程)并将结果写入上下文数据。业务逻辑处理函数执行完成或被取消后,触发EventLoop执行另一函数,函数从上下文数据中获取结果,转换为JS类型,调用JS回调函数或通过Promise resolve()返回结果。

相较原生线程优势

  • 线程管理:NodeJs提供了一个抽象层,不需要手动管理线程的生命周期或同步问题。napi_create_async_work 会在后台执行任务,并在任务完成后自动回调到 JavaScript 线程。Node.js 本身使用了 libuv 作为事件驱动机制,napi_create_async_work 会将任务放入 libuv 的线程池中执行,并在任务完成后通过事件循环调用回调。
  • 内存管理:NodeJs 的垃圾回收机制与异步工作结合良好。napi_create_async_work 确保了在任务完成之前,任务相关的资源不会被提前回收。任务完成后,N-API 会自动管理与 JavaScript 对象的资源同步,提供了便捷的内存和生命周期管理。
  • 回调机制:napi_create_async_work为 JavaScript 提供了一种与 C++ 异步工作交互的回调机制。任务执行完毕后,回调函数会在主线程中被执行,可以直接将任务结果传回 JavaScript 端。这种机制使得异步工作能够无缝地与 JavaScript 主线程事件循环集成。
  • 线程池与并发:napi_create_async_work利用了 Node.js 的 libuv 线程池,无需开发者手动创建和管理线程。Node.js 的线程池会智能调度工作任务,我们不必担心线程池的大小或并发量。
  • 错误处理:N-API 提供了较为简单的错误处理方式,任务执行期间的错误可以在回调函数中捕获并传递回 JavaScript 层。开发者可以轻松将本地(Native)代码中的错误状态传递给 JavaScript 层进行处理。

异步接口实现

以音频编码为例展示异步接口。

Callback 异步接口方式

示例接口的eTS定义

export const encodePcmDataForOpusWithOgg:(intputBuffer: ArrayBuffer, callback:() => void) => void;

传入PCM数据,通过编码完成直接写入文件,并同步TS层执行状态(也可以返回编码后数据)。

初始化上下文数据

根据业务需求自定义一个上下文数据结构,用于保存和传递数据。本例自定义的上下文数据包含:异步工作项对象、回调函数、2个参数等4个属性。

struct EncodeAudioData {  
    napi_async_work asyncWork = nullptr;  
    napi_ref callback = nullptr;  
    void* inputBuffer = nullptr;  
    size_t intputSize = 0;  
}

创建异步工作项

在创建异步工作项前,我们先分别声明2个函数,分别用作于napi_create_async_work()函数的execute、complete参数。异步工作项创建好,将它存入上下文数据的asyncWork属性,并调用napi_queue_async_work()将异步工作项加入调度队列,由异步work线程池统一调度,原生方法返回空值退出。

static napi_value encodePCMToOpusOggNative(napi_env env, napi_callback_info info) {  
    size_t argc = 2;  
    napi_value args[2] = {nullptr};  
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);  
    
    auto audioData = new EncodeAudioData{  
      .asyncWork = nullptr,  
    };  
    
    napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &(audioData->intputSize));   
    napi_create_reference(env, args[1], 1, &audioData->callback);  
    napi_value resourceName = nullptr;  
    napi_create_string_utf8(env, "encodePCMToOpusOggNative", NAPI_AUTO_LENGTH, &resourceName);  
    napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodeAsyncCompleteCB, (void *)audioData, &audioData->asyncWork);  
  
    napi_queue_async_work(env, audioData->asyncWork);  
    return nullptr;  
}

execute 函数

// 业务逻辑处理函数,由worker线程池调度执行。
static void aencodeExecuteCB(napi_env env, void *data) {
    EncodeAudioData *encodeAudioData = (EncodeAudioData *)data;
    // 执行音频编码和写入文件  
    ope_encoder_write(pEnc, (short *)(encodeAudioData->inputBuffer), encodeAudioData->intputSize/2);  
}

complete 函数

// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。 
static void encodeAsyncCompleteCB(napi_env env, napi_status status, void *data) {  
    EncodeAudioData *encodeAudioData = (EncodeAudioData *)data;  
    napi_value callback = nullptr;  
    napi_value undefined = nullptr;  
    napi_get_undefined(env, &undefined);  
    napi_get_reference_value(env, encodeAudioData->callback, &callback);  
    napi_value callbackResult;  
    // 回调TS
    napi_call_function(env, undefined, callback, 0, nullptr, &callbackResult);  

    // 释放资源
    if (encodeAudioData->callback != nullptr) {  
        napi_delete_reference(env, encodeAudioData->callback);  
    }  
  
    napi_delete_async_work(env, encodeAudioData->asyncWork);  
    delete encodeAudioData;  
}

eTS调用接口

static encodePcmDataForOpusOgg(inputBuffer: ArrayBuffer):void {  
  opusOggEnc.encodePcmDataForOpusWithOgg(inputBuffer, ()=>{  
  
  });  
}

Promise 接口方式

通过前面异步方式实现原理我们可知Promise整体处理流程和Callback方式一样。不同的是,首先要创建一个Promise。NAPI框架中提供了napi_create_promise()函数用于创建Promise,调用该函数输出2个对象——deferred、promise。promise用于原生方法返回,deferred传入异步工作项的上下文数据。complete函数中,应用napi_resolve_deferred()函数 或 napi_reject_deferred() 函数返回数据。

创建Promise

static napi_value encodePromise(napi_env env, napi_callback_info info) {  
  // 创建promise
  napi_value promise = nullptr;
  napi_deferred deferred = nullptr;
  napi_create_promise(env, &deferred, &promise);

  ...

  // 返回promise
  return promise;
}

初始化上下文数据

同Callback方式定义一个上下文数据结构,用于保存和传递数据。Promise方式去掉callback属性,加上deferred属性。

struct EncodeAudioData {  
    napi_async_work asyncWork = nullptr;  
    napi_deferred deferred = nullptr;
    void* inputBuffer = nullptr;  
    size_t intputSize = 0;  
}

创建异步工作项

static napi_value encodePromise(napi_env env, napi_callback_info info) {  
    size_t argc = 2;  
    napi_value args[2] = {nullptr};  
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);  
    // 创建promise
    napi_value promise = nullptr;
    napi_deferred deferred = nullptr;
    napi_create_promise(env, &deferred, &promise);
    
    // 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
    auto audioData = new EncodeAudioData{  
      .asyncWork = nullptr,
      .deferred = deferred,
    };  
...
}

execute 回调处理

// 业务逻辑处理函数,由worker线程池调度执行。
static void encodeExecuteCB(napi_env env, void *data) {
    EncodeAudioData *encodeAudioData = (EncodeAudioData *)data;
    // 执行音频编码和写入文件  
    ope_encoder_write(pEnc, (short *)(encodeAudioData->inputBuffer), encodeAudioData->intputSize/2);  
}

complete 回调处理

static void encodePromiseCompleteCB(napi_env env, napi_status status, void *data) {
    EncodeAudioData *encodeAudioData = (EncodeAudioData *)data;  
    napi_value undefined;  
    napi_get_undefined(env, &undefined);  
    napi_resolve_deferred(env, encodeAudioData->deferred, undefined);  
    // 删除napi_ref对象  
    if (encodeAudioData->callback != nullptr) {  
        napi_delete_reference(env, encodeAudioData->callback);  
    }  
    // 删除异步工作项  
    napi_delete_async_work(env, encodeAudioData->asyncWork);  
    delete encodeAudioData;  
    encodeAudioData = nullptr;
}

eTS调用接口

static encodePcmDataForOpusOgg(inputBuffer: ArrayBuffer):void {  
  opusOggEnc.encodePcmDataForOpusWithOggPromise(inputBuffer).then(()=>{  
  
  });  
}

规范异步接口

若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式,通过判断接收到的参数个数判断是Callback方式还是Promise方式。

export function encodePcmDataForOpusWithOgg(intputBuffer: ArrayBuffer, callback:() => void): void;  
export function encodePcmDataForOpusWithOgg(intputBuffer: ArrayBuffer): Promise<void>;

首先修改用户上下文数据结构,同时包含deferred、callback属性。

struct EncodeAudioData {  
    napi_async_work asyncWork = nullptr;  
    napi_ref callback = nullptr;  
    napi_deferred deferred = nullptr;  
    void* inputBuffer = nullptr;  
    size_t intputSize = 0;  
}

修改接口原生方法实现,通过判断实际获取到的参数个数判断是Callback还是Promise,根据上面的接口定义,2个参数是Promise,3个参数是Callback。

static napi_value encodePCMToOpusOggNative(napi_env env, napi_callback_info info) {  
    size_t argc = 2;  
    napi_value args[2] = {nullptr};  
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);  
    auto audioData = new EncodeAudioData{  
      .asyncWork = nullptr,  
    };  
    if (argc == 1) {  
        // 创建promise  
        napi_value promise = nullptr;  
        napi_deferred deferred = nullptr;  
        napi_create_promise(env, &deferred, &promise);  
        audioData->deferred = deferred;  
        napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &(audioData->intputSize));   
        napi_create_reference(env, args[0], 1, &audioData->callback);  
        napi_value resourceName = nullptr;  
        napi_create_string_utf8(env, "encodePCMToOpusOggNativePromise", NAPI_AUTO_LENGTH, &resourceName);  
        napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodePromiseCompleteCB, (void *)audioData, &audioData->asyncWork);  
        napi_queue_async_work(env, audioData->asyncWork);  
        // 返回promise  
        return promise;  
    }else{  
        napi_get_arraybuffer_info(env, args[0], &audioData->inputBuffer, &(audioData->intputSize));   
        napi_create_reference(env, args[1], 1, &audioData->callback);  
        napi_value resourceName = nullptr;  
        napi_create_string_utf8(env, "encodePCMToOpusOggNativeCallback", NAPI_AUTO_LENGTH, &resourceName);  
        napi_create_async_work(env, nullptr, resourceName, encodeExecuteCB, encodeAsyncCompleteCB, (void *)audioData, &audioData->asyncWork);  
        napi_queue_async_work(env, audioData->asyncWork);  
        return nullptr;    
    }  
}

更多关于HarmonyOS鸿蒙Next NAPI异步调用入门的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在鸿蒙Next中,异步调用的核心是使用napi_create_async_worknapi_queue_async_worknapi_delete_async_work等函数。这些函数允许在Native层创建、执行和销毁异步任务。具体步骤如下:

  1. 创建异步任务:使用napi_create_async_work创建一个异步工作对象。该函数需要提供执行函数(execute)和完成回调函数(complete)。execute函数在异步线程中执行耗时操作,complete函数在主线程中执行,用于处理异步操作的结果。

  2. 提交异步任务:通过napi_queue_async_work将异步任务提交到任务队列中,等待执行。任务会在后台线程中运行,不会阻塞主线程。

  3. 处理异步结果:在complete回调函数中,可以将异步操作的结果传递回ArkTS层。通常使用napi_get_value_*系列函数从Native层获取数据,并通过napi_call_function调用ArkTS中的回调函数。

  4. 清理异步任务:异步任务执行完毕后,使用napi_delete_async_work释放资源。

以下是一个简单的示例,展示了如何在Native层实现异步调用:

#include <napi.h>

void ExecuteWork(napi_env env, void* data) {
    // 模拟耗时操作
    sleep(2);
}

void CompleteWork(napi_env env, napi_status status, void* data) {
    napi_value undefined;
    napi_get_undefined(env, &undefined);
    napi_value callback = (napi_value)data;
    napi_call_function(env, undefined, callback, 0, nullptr, nullptr);
}

void AsyncCall(napi_env env, napi_callback_info info) {
    napi_value work_name;
    napi_create_string_utf8(env, "AsyncWork", NAPI_AUTO_LENGTH, &work_name);

    napi_value callback;
    napi_get_cb_info(env, info, nullptr, nullptr, &callback, nullptr);

    napi_async_work work;
    napi_create_async_work(env, nullptr, work_name, ExecuteWork, CompleteWork, callback, &work);
    napi_queue_async_work(env, work);
}

NAPI_MODULE_INIT() {
    napi_value fn;
    napi_create_function(env, nullptr, 0, AsyncCall, nullptr, &fn);
    napi_set_named_property(env, exports, "asyncCall", fn);
    return exports;
}

在ArkTS中,可以通过以下方式调用该异步函数:

import nativeModule from 'libnative.so';

nativeModule.asyncCall(() => {
    console.log('Async operation completed.');
});

通过NAPI实现异步调用,可以有效提升应用的响应速度和性能。

更多关于HarmonyOS鸿蒙Next NAPI异步调用入门的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


HarmonyOS鸿蒙Next的NAPI(Native API)允许开发者进行异步调用,以提升应用性能。首先,通过napi_create_async_work创建异步工作对象,定义异步任务。然后,使用napi_queue_async_work将任务加入队列,系统会在后台执行。任务完成后,通过napi_delete_async_work释放资源。异步调用的关键在于正确处理回调函数,确保数据同步和错误处理。开发者需熟悉Node.js的异步编程模型,以便在鸿蒙平台上高效实现异步操作。

回到顶部