napi系列学习进阶篇——NAPI异步调用

2024-04-14 07:28

本文主要是介绍napi系列学习进阶篇——NAPI异步调用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

OpenHarmony Napi 标准系统异步接口实现支持Callback方式和Promise方式。标准系统异步接口实现规范要求,若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式。使用哪种方式由应用开发者决定,通过是否传递Callback函数进行区分。不传递Callback即为Promise方式,方法执行结果为Promise实例对象。

异步方式实现原理

  • 异步方式原理 同步方式,所有的代码处理都在原生方法(主线程)中完成。异步方式依赖NAPI框架提供的napi_create_async_work()函数创建异步工作项,原生方法被调用时,原生方法完成数据接收、转换,存入上下文数据,之后创建一个异步工作项,并加入调度队列,由异步工作线程池统一调度,原生方法返回空值(Callback方式)或返回Promise对象(Promise方式)。异步工作项中定义了2个函数,一个用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在worker线程中完成业务逻辑计算(不阻塞主线程)并将结果写入上下文数据。业务逻辑处理函数执行完成或被取消后,触发EventLoop执行另一函数,函数从上下文数据中获取结果,转换为JS类型,调用JS回调函数或通过Promise resolve()返回结果。

  • 异步方式处理流程图

  • 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表示转换成功,其他值失败。

Callback 异步接口

下面基于napi_create_async_work将add()接口改成Callback方式接口——addCallback(),接口的eTS定义

function addAsyncCallback(numX: number, numY: number, callback:(result: number) => void): void;

初始化上下文数据

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

struct AddonData {napi_async_work asyncWork = nullptr;napi_ref callback = nullptr;double args[2] = {0};double result = nullptr;
};

在 napi数据类型 文中,我们已了解对于NAPI框架,所有参数,无论是ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型,还是Function类型,都已统一封装为napi_value类型,故可如获取数据类型的参数一样获取Function类型参数,本例直接调用函数获取3个参数——加数、被加数、回调函数。

接着我们将接收到的参数转换存入上下文数据,number类型的转换为double直接存入即可。Function类型的参数怎么处理?不转换直接存入napi_value类型?答案是不行的!这牵涉到NAPI对象生命周期管理问题。napi_value类型引用对象的生命周期在原生方法退出后结束,后面在work线程无法获取其值。NAPI提供了一种生命期限长于原生方法的对象引用类型—— napi_ref,napi_ref引用对象在原生方法退出后不自动回收,由用户管理此类型对象的生命周期。所以当前方法中,我们调用napi_create_reference()函数将接收到的napi_value类型的回调函数参数args[2]转换为napi_ref类型(生命周期具体定义及使用可参照文档napi生命周期)。

static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {// 获取3个参数,值的类型是js类型(napi_value)size_t argc = 3;napi_value args[3];napi_value thisArg = nullptr;napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr);...// 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据auto addonData = new AddonData{.asyncWork = nullptr,};// 将接收到的参数传入用户自定义上下文数据napi_get_value_double(env, args[0], &addonData->args[0]);napi_get_value_double(env, args[1], &addonData->args[1]);napi_create_reference(env, args[2], 1, &addonData->callback);...
}

创建异步工作项

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

// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {
}// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发。
static void addAsyncCompleteCB(napi_env env, napi_status status, void *data) {
}static napi_value addAsyncCallback(napi_env env, napi_callback_info info) {...napi_create_reference(env, args[2], 1, &addonData->callback);// 创建async work,创建成功后通过最后一个参数接收async work的handlenapi_value resourceName = nullptr;napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addAsyncCompleteCB, (void *)addonData, &addonData->asyncWork);// 将刚创建的async work加到队列,由work thread调度执行napi_queue_async_work(env, addonData->asyncWork);// 原生方法返回空对象napi_value result = 0;napi_get_null(env, &result);return result;
}

execute 函数

execute函数在异步工作项被调度后在work线程中执行,不阻塞主线程(不阻塞UI界面),可执行IO、CPU密集型等任务。此处仅为演示,我们的业务逻辑计算就是一个简单的加法,并把计算结果存入上下文数据的result属性。

// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {AddonData *addonData = (AddonData *)data;// 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。addonData->result = addonData->args[0] + addonData->args[1];
}

complete 函数

从接收到的上下文数据中获取结果,调用napi_call_function()方法执行JS回调函数返回数据给JS。之后释放过程中创建的napi_ref引用对象、异步工作项等对象。 NAPI框架提供了napi_call_function()函数供扩展Natvie代码(C/C++代码)调用JS函数,用于执行回调函数等场景。函数定义如下:

NAPI_EXTERN napi_status napi_call_function(napi_env env,napi_value recv,napi_value func,size_t argc,const napi_value* argv,napi_value* result);

参数说明:

  • [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
  • [in] recv: 传给被调用的this对象。
  • [in] func: 被调用的函数.
  • [in] argc: 函数参数个数(对应函数数组的长度)。
  • [in] argv: 函数参数数组.
  • [out] result: func函数执行的返回值。 返回值:返回napi_ok表示转换成功,其他值失败。 因对象生命周期管理问题,上下文数据的callback属性的类型为napi_ref,需要调用napi_get_reference_value()函数获取其指向的napi_value对象值才调用napi_call_function()函数。napi_get_reference_value()函数介绍参照文档napi生命周期。

complete接口实现:

// 业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
static void addAsyncCompleteCB(napi_env env, napi_status status, void *data) {AddonData *addonData = (AddonData *)data;napi_value callback = nullptr;napi_value undefined = nullptr;napi_get_undefined(env, &undefined);napi_get_reference_value(env, loginAddonData->callback, &callback);napi_call_function(env, undefined, callback, 0, nullptr, &callbackResult);// 删除napi_ref对象if (loginAddonData->callback != nullptr) {napi_delete_reference(env, loginAddonData->callback);}// 删除异步工作项napi_delete_async_work(env, loginAddonData->asyncWork);delete loginAddonData;
}

eTS调用接口

import testNapi from "libentry.so";@Entry
@Component
struct Index {build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold).onClick(() => {let num1 = 123, num2 = 456testNapi.addCallback(num1, num2, (result) =>{console.info("message: 123 + 456  = " + result)})})}.width('100%')}.height('100%')
}

Promise 接口

创建Promise

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

napi_status napi_create_promise(napi_env env,napi_deferred* deferred,napi_value* promise);

参数说明:

  • [in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
  • [out] deferred: 返回接收刚创建的deferred对象,关联Promise对象,后面使用napi_resolve_deferred() 或 napi_reject_deferred() 返回数据。
  • [out] promise: 关联上面deferred对象的JS Promise对象 返回值:返回napi_ok表示转换成功,其他值失败。

创建Promise接口的实现:

static napi_value addPromise(napi_env env, napi_callback_info info) {// 创建promisenapi_value promise = nullptr;napi_deferred deferred = nullptr;NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));...// 返回promisereturn promise;
}

初始化上下文数据

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

// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData {napi_async_work asyncWork = nullptr;napi_deferred deferred = nullptr;double args[2] = {0};double result = nullptr;
};static napi_value addPromise(napi_env env, napi_callback_info info) {// 获取2个参数,值的类型是js类型(napi_value)size_t argc = 2;napi_value args[2];napi_value thisArg = nullptr;NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));...// 创建promisenapi_value promise = nullptr;napi_deferred deferred = nullptr;NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));// 异步工作项上下文用户数据,传递到异步工作项的execute、complete之间传递数据auto addonData = new AddonData{.asyncWork = nullptr,.deferred = deferred,};// 将接收到的参数传入NAPI_CALL(env, napi_get_value_double(env, args[0], &addonData->args[0]));NAPI_CALL(env, napi_get_value_double(env, args[1], &addonData->args[1]));...
}

创建异步工作项

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

// 用户提供的上下文数据,在原生方法(初始化数据)、executeCB、completeCB之间传递数据
struct AddonData {napi_async_work asyncWork = nullptr;napi_deferred deferred = nullptr;double args[2] = {0};double result = 0;
};static napi_value addPromise(napi_env env, napi_callback_info info) {...// 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handlenapi_value resourceName = nullptr;napi_create_string_utf8(env, "addAsyncCallback", NAPI_AUTO_LENGTH, &resourceName);napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,&addonData->asyncWork);// 将刚创建的async work加到队列,由底层去调度执行napi_queue_async_work(env, addonData->asyncWork);// 原生方法返回promisereturn promise;
}

execute 回调处理

此处完全同Callback方式,无需修改。

// 业务逻辑处理函数,由worker线程池调度执行。
static void addExecuteCB(napi_env env, void *data) {AddonData *addonData = (AddonData *)data;// 执行复杂计算,不阻塞主线程。此处用一个加法简单示意。addonData->result = addonData->args[0] + addonData->args[1];
}

complete 回调处理

调用NAPI提供的napi_resolve_deferred() 或 napi_reject_deferred() 返回数据。之后释放过程中创建的napi_ref引用对象、异步工作项等对象。

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

eTS调用接口

import testNapi from "libentry.so";@Entry
@Component
struct Index {build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold).onClick(() => {let num1 = 123, num2 = 456testNapi.addPromise(num1, num2).then((result) =>{console.info("message: 123 + 456  = " + result)})})}.width('100%')}.height('100%')
}

规范异步接口

如本文开头所说,若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式,通过判断接收到的参数个数判断是Callback方式还是Promise方式。下面我们将addCallbak()、addPromise() 2个接口合并成一个接口——addAsync(),接口的eTS定义:

function addAsync(num1: number, num2: number, callback:(result: number) => void): void;
function addAsync(num1: number, num2: number): Promise<number>;

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

struct AddonData {napi_async_work asyncWork = nullptr;napi_deferred deferred = nullptr;napi_ref callback = nullptr;double args[2] = {0};double result = 0;
};

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

static napi_value addAsync(napi_env env, napi_callback_info info) {// 获取3个参数,值的类型是js类型(napi_value)size_t argc = 3;napi_value args[3];napi_value thisArg = nullptr;napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr);// 获取并判断js参数类型napi_valuetype valuetype0;napi_typeof(env, args[0], &valuetype0);napi_valuetype valuetype1;napi_typeof(env, args[1], &valuetype1);if (valuetype0 != napi_number || valuetype1 != napi_number) {napi_throw_type_error(env, nullptr, "Wrong arguments. 2 numbers expected.");return NULL;}// 异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据auto addonData = new AddonData{.asyncWork = nullptr,};// 判断事件获取的参数个数,如是2个则按Promise处理。if (argc == 2) {// 创建promisenapi_value promise = nullptr;napi_deferred deferred = nullptr;napi_create_promise(env, &deferred, &promise);addonData->deferred = deferred;// 将接收到的参数传入napi_get_value_double(env, args[0], &addonData->args[0]);napi_get_value_double(env, args[1], &addonData->args[1]);// 创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回async work的handlenapi_value resourceName = nullptr;napi_create_string_utf8(env, "addPromise", NAPI_AUTO_LENGTH, &resourceName);napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addPromiseCompleteCB, (void *)addonData,&addonData->asyncWork);// 将刚创建的async work加到队列,由底层去调度执行napi_queue_async_work(env, addonData->asyncWork);// 返回promisereturn promise;} else {napi_valuetype valuetype2;napi_typeof(env, args[2], &valuetype2);if (valuetype2 != napi_function) {napi_throw_type_error(env, nullptr, "Callback function expected.");return nullptr;}// 将接收到的参数传入用户自定义上下文数据napi_get_value_double(env, args[0], &addonData->args[0]);napi_get_value_double(env, args[1], &addonData->args[1]);napi_create_reference(env, args[2], 1, &addonData->callback);// 创建async work,创建成功后通过最后一个参数接收async work的handlenapi_value resourceName = nullptr;napi_create_string_utf8(env, "addCallback", NAPI_AUTO_LENGTH, &resourceName);napi_create_async_work(env, nullptr, resourceName, addExecuteCB, addCallbackCompleteCB, (void *)addonData,&addonData->asyncWork);// 将刚创建的async work加到队列,由底层去调度执行napi_queue_async_work(env, addonData->asyncWork);// 原生方法返回空对象napi_value result = 0;napi_get_null(env, &result);return result;}
}

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

这篇关于napi系列学习进阶篇——NAPI异步调用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/902433

相关文章

Python异步编程之await与asyncio基本用法详解

《Python异步编程之await与asyncio基本用法详解》在Python中,await和asyncio是异步编程的核心工具,用于高效处理I/O密集型任务(如网络请求、文件读写、数据库操作等),接... 目录一、核心概念二、使用场景三、基本用法1. 定义协程2. 运行协程3. 并发执行多个任务四、关键

Java调用Python脚本实现HelloWorld的示例详解

《Java调用Python脚本实现HelloWorld的示例详解》作为程序员,我们经常会遇到需要在Java项目中调用Python脚本的场景,下面我们来看看如何从基础到进阶,一步步实现Java与Pyth... 目录一、环境准备二、基础调用:使用 Runtime.exec()2.1 实现步骤2.2 代码解析三、

Python如何调用另一个类的方法和属性

《Python如何调用另一个类的方法和属性》在Python面向对象编程中,类与类之间的交互是非常常见的场景,本文将详细介绍在Python中一个类如何调用另一个类的方法和属性,大家可以根据需要进行选择... 目录一、前言二、基本调用方式通过实例化调用通过类继承调用三、高级调用方式通过组合方式调用通过类方法/静

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

Python用Flask封装API及调用详解

《Python用Flask封装API及调用详解》本文介绍Flask的优势(轻量、灵活、易扩展),对比GET/POST表单/JSON请求方式,涵盖错误处理、开发建议及生产环境部署注意事项... 目录一、Flask的优势一、基础设置二、GET请求方式服务端代码客户端调用三、POST表单方式服务端代码客户端调用四

C#异步编程ConfigureAwait的使用小结

《C#异步编程ConfigureAwait的使用小结》本文介绍了异步编程在GUI和服务器端应用的优势,详细的介绍了async和await的关键作用,通过实例解析了在UI线程正确使用await.Conf... 异步编程是并发的一种形式,它有两大好处:对于面向终端用户的GUI程序,提高了响应能力对于服务器端应

Python跨文件实例化、跨文件调用及导入库示例代码

《Python跨文件实例化、跨文件调用及导入库示例代码》在Python开发过程中,经常会遇到需要在一个工程中调用另一个工程的Python文件的情况,:本文主要介绍Python跨文件实例化、跨文件调... 目录1. 核心对比表格(完整汇总)1.1 自定义模块跨文件调用汇总表1.2 第三方库使用汇总表1.3 导

C# async await 异步编程实现机制详解

《C#asyncawait异步编程实现机制详解》async/await是C#5.0引入的语法糖,它基于**状态机(StateMachine)**模式实现,将异步方法转换为编译器生成的状态机类,本... 目录一、async/await 异步编程实现机制1.1 核心概念1.2 编译器转换过程1.3 关键组件解析

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

Python调用LibreOffice处理自动化文档的完整指南

《Python调用LibreOffice处理自动化文档的完整指南》在数字化转型的浪潮中,文档处理自动化已成为提升效率的关键,LibreOffice作为开源办公软件的佼佼者,其命令行功能结合Python... 目录引言一、环境搭建:三步构建自动化基石1. 安装LibreOffice与python2. 验证安装