三方库移植之NAPI开发系列文章《Hello OpenHarmony NAPI》、《C/C++与JS的数据类型转换》其接口都是同步的。对IO、CPU密集型任务需要异步处理。 NAPI支持异步模型,提供了Promise、Callback 2种方式。
- 计算密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。
- CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
- IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。
写在开头:
- 本文在三方库移植之NAPI开发[1]—Hello OpenHarmony NAPI的基础上修改hellonapi.cpp、index.ets,接着学习NAPI异步模型的Promise、Callback方式。
- 本文共有三个示例,分别是Callback 异步接口示例、Promise 异步接口示例、规范异步接口示例。在本文末尾的资源中提供了这三个示例的源代码,读者可以下载在开发板上运行。
- 开发基于最新的OpenHarmony3.2Beta3版本及API9,标准系统开发板为润和软件DAYU200。
NAPI异步方式实现原理
- 同步方式和异步方式:
同步方式,所有的代码处理都在原生方法(主线程)中完成。
异步方式,所有的代码处理在多个线程中完成。
实现NAPI异步方法的步骤:
立即返回一个临时结果给js调用者
另起线程完成异步业务逻辑的执行
通过callback或promise返回真正的结果
- 异步工作项工作时序图:
- 原生方法被调用时,原生方法完成数据接收、数据类型转换、存入上下文数据,之后创建异步工作项。
- 异步工作项会加入调度队列,由异步工作线程池统一调度,原生方法返回空值(Callback方式)或返回Promise对象(Promise方式)。
- 异步方式依赖NAPI框架提供的napi_create_async_work()函数创建异步工作项napi_create_async_work()在foundation/arkui/napi/native_engine/native_node_api.cpp第71行。
napi_create_async_work里有两个回调:
- execute:用于异步处理业务逻辑。因为不在JS线程中,所以不允许调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。
- complete:可以调用napi的接口,将execute中的返回值封装成JS对象返回。此回调在JS线程中执行。
管理简单的异步操作的方法还有这些
- NAPI_EXTERN napi_status napi_delete_async_work(napi_env env, napi_async_work work)
- NAPI_EXTERN napi_status napi_queue_async_work(napi_env env, napi_async_work work)
- NAPI_EXTERN napi_status napi_cancel_async_work(napi_env env, napi_async_work work)
- 异步工作项中定义了2个函数
- 一个函数用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在worker线程中完成业务逻辑计算(不阻塞主线程)并将结果写入上下文数据。
- 业务逻辑处理函数执行完成或被取消后,触发EventLoop执行另一个函数,该函数从上下文数据中获取结果,转换为JS类型,调用JS回调函数或通过Promise resolve()返回结果。
NAPI支持异步模型
- NAPI支持异步模型,提供了Promise、Callback方式。OpenHarmony标准系统异步接口实现支持Promise方式和Callback方式。
- 标准系统异步接口实现规范要求,若引擎开启Promise特性支持,则异步方法必须同时支持Callback方式和Promise方式。
1.由应用开发者决定使用哪种方式,通过是否传递Callback函数区分异步方法是Callback方式还是Promise方式。
2.不传递Callback即为Promise方式(方法执行结果为Promise实例对象),否则为Callback方式, - ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案回调函数和事件;
promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
在返回的结果的个数超过一个时,其以对象属性的形式返回。
ES6:全称ECMAScript 6.0。ECMAScript 是JavaScript语言的国际标准,JavaScript是ECMAScript的实现。
- Promise 异步模型
- ES6原生提供了Promise对象,Promise是异步编程的一种解决方案,可以替代传统的解决方案回调函数和事件;
promise对象是一个异步操作的结果,提供了一些API使得异步执行可以按照同步的流表示出来,避免了层层嵌套的回调函数,保证了回调是以异步的方式进行调用的;
用户在调用这些接口的时候,接口实现将异步执行任务,同时返回一个 Promise 对象,其代表异步操作的结果;
在返回的结果的个数超过一个时,其以对象属性的形式返回。 - Promise特点: 作为对象,Promise有两个特点:
对象的状态不受外界影响;
一旦状态改变了就不会再变,也就是说任何时候Promise都只有一种状态。 - Callback 异步模型
用户在调用这些接口的时候,接口实现将异步执行任务,任务执行结果以参数的形式提供给用户注册的回调函数,这些参数的第一个是 Error 或 undefined 类型,分别表示执行出错与正常。
Callback 异步接口
Callback 异步接口示例完成代码
hellonapi.cpp文件:
index.ets:
@ohos.hellonapi.d.ts:
初始化上下文数据
- 定义异步工作项上下文数据,根据业务需求自定义一个定义异步工作项上下文数据结构,用于在主线程方法、Work线程、EventLoop线程之间传递数据。
- 本示例定义的上下文数据包含:异步工作项对象、回调函数、2个参数(加数、被加数)、业务逻辑处理结果等4个属性。
NAPI框架将ECMAScript标准中定义的Boolean、Null、Undefined、Number、BigInt、String、Symbol和Object八种数据类型和Function类型,都已统一封装为napi_value类型,故可如获取数据类型的参数一样获取Function类型参数
Function是JavaScript提供的一种引用类型,通过Function类型创建Function对象。
在JavaScript中,函数也是以对象的形式存在的,每个函数都是一个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_create_reference()函数定义如下:
参数说明:
[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。
[in] value: 需要创建一个引用的napi_value对象
[in] initial_refcount: 初始化引用次数。
[out] result: 指针,指向新创建的napi_ref对象。 返回值:返回napi_ok表示转换成功,其他值失败。
napi_create_reference() : 将napi_value包装成napi_ref引用对象
napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
napi_delete_reference() :删除napi_ref引用对象
NAPI框架给出的解决方案是让开发者通过napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方再将napi_ref还原为napi_value,用完后再删除引用对象以便释放相关内存资源。
需要跨作用域传递napi_value时,往往需要用到上面这组方法把napi_value变成napi_ref。这是因为napi_value本质上只是一个指针,指向某种类型的napi数据对象。NAPI框架希望通过这种方式为开发者屏蔽各种napi数据对象的类型细节,类似于void* ptr的作用 。既然是指针,使用时就需要考虑它指向的对象的生命周期。
创建异步工作项
- 第一步:在创建异步工作项前,分别声明addExecuteCB、addAsyncCompleteCB这2个函数,分别用作于napi_create_async_work(napi_env env,napi_value async_resource,napi_value async_resource_name,napi_async_execute_callbackexecute,napi_async_complete_callbackcomplete,void* data,napi_async_work* result)函数的execute、complete参数。
- 第二步:利用NAPI框架提供的napi_create_async_work()函数创建异步工作项,将addExecuteCB、addAsyncCompleteCB这2个函数存入上下文数据的asyncWork属性
- 第三步:调用napi_queue_async_work()将异步工作项加入调度队列,由异步work线程池统一调度,原生方法返回空值退出。
execute函数
创建异步工作项前,声明了addExecuteCB这个函数,用作于napi_create_async_work()函数的execute参数。
- execute函数在异步工作项被调度后在work线程中执行。
- 不阻塞主线程(不阻塞UI界面)。
- 可执行IO、CPU密集型等任务。
- 业务逻辑计算是一个简单的加法,并把计算结果存入上下文数据的result属性。
complete 函数
创建异步工作项前,声明addAsyncCompleteCB这个函数,用作于napi_create_async_work()函数的complete参数。
- 第一步:addAsyncCompleteCB从接收到的上下文数据中获取结果,调用napi_call_function()方法执行JS回调函数返回数据给JS。
- 第二步释放(删除)过程中创建的napi_ref引用对象、异步工作项等对象。
NAPI框架提供了napi_call_function()函数供扩展Natvie代码(C/C++代码)调用JS函数,用于执行回调函数等场景。函数定义如下:
因对象生命周期管理问题,上下文数据的callback属性的类型为napi_ref,需要调用napi_get_reference_value()函数获取其指向的napi_value对象值才调用napi_call_function()函数。 napi_get_reference_value函数定义:
总结
Promise异步接口
hellonapi.cpp:
index.ets:
@ohos.hellonapi.d.ts:
规范异步接口
hellonapi.cpp:
index.ets:
@ohos.hellonapi.d.ts:
NAPI中的数据类型
- NAPI使用的数据类型和Node.js N-API保持一致。OpenHarmony的NAPI(Native API)组件是一套对外接口基于Node.js N-API规范开发的原生模块扩展开发框架。
- 通过查看foundation/arkui/napi/interfaces/inner_api/napi/native_node_api.h(编写NAPI拓展模块hellonapi.cpp需要包含的头文件)可以知道OpenHarmony基本的NAPI数据类型。
- #include 中的js_native_api.h在ohos3.2beta3版本源码目录下路径为prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h。
- 然后再分析prebuilts/build-tools/common/nodejs/node-v12.18.4-linux-x64/include/node/js_native_api_types.h和third_party/node/src/js_native_api_types.h内容的差别。
- 两者内容一致,可以推测OpenHarmony中基本的NAPI数据类型和Node.js N-API中的保持一致。而接口名方面,napi提供的接口名与三方Node.js一致,目前支持部分接口,详情见libnapi.ndk.json文件
预处理器发现 #include 指令后,就会寻找指令后面<>中的文件名,并把这个文件的内容包含到当前文件中。被包含文件中的文本将替换源代码文件中的#include 指令
- 以typedef struct napi_env__* napi_env为例,搜遍Node.js的源码都找不到napi_value__定义,那这个定义是什么意思呢?c语言中,允许定义一个没有定义的结构体的指针。所以napi_value其实就是一个一级指针。他不需要类型信息。
- 在callback回调方式的处理流程中,用到了这3个与napi_ref相关的方法:
napi_create_reference() : 将napi_value包装成napi_ref引用对象
napi_get_reference_value() : 从napi_ref引用对象中取得napi_value
napi_delete_reference() :删除napi_ref引用对象
当我们需要跨作用域传递napi_value时,往往需要用到上面这组方法把napi_value变成napi_ref。这是因为napi_value本质上只是一个指针,指向某种类型的napi数据对象。NAPI框架希望通过这种方式为开发者屏蔽各种napi数据对象的类型细节,类似于void* ptr的作用 。既然是指针,使用时就需要考虑它指向的对象的生命周期。
在我们的例子中,我们通过GetVisitCountAsync()方法的入参得到了js应用传递给C++的 callback function,存放在napi_value argv[1]中。但我们不能在complete_callback()方法中直接通过这个argv[1]去回调callback function(通过data对象传递也不行)。这时因为当代码执行到complete_callback()方法时,原先的主方法GetVisitCountAsync()早已执行结束, napi_value argv[1]指向的内存可能已经被释放另作他用了。
NAPI框架给出的解决方案是让开发者通过napi_value创建一个napi_ref,这个napi_ref是可以跨作用域传递的,然后在需要用到的地方再将napi_ref还原为napi_value,用完后再删除引用对象以便释放相关内存资源。
typedef作用就是定义类型别名
http://nodejs.cn/api/n-api.html。
参考文章
https://ost.51cto.com/posts/14691。
关于NAPI标准库中导出的符号列表
- NAPI它基于Node.js N-API规范开发,因此可参考Node.js N-API了解NAPI标准库中符号列表。本文以3.2beta3源码中的node三方库为例,从third_party/node/README.OpenSource中可得知3.2beta3移植的node版本为14.19.1,因此可参考的Node.js N-API链接为14.19.1版本,如下:https://nodejs.org/docs/latest-v14.x/api/n-api.html。
- 标准库中导出的符号列表
符号类型 | 符号名 | 备注 |
FUNC | napi_module_register | |
FUNC | napi_get_last_error_info | |
FUNC | napi_throw | |
FUNC | napi_throw_error | |
FUNC | napi_throw_type_error | |
FUNC | napi_throw_range_error | |
FUNC | napi_is_error | |
FUNC | napi_create_error | |
FUNC | napi_create_type_error | |
FUNC | napi_create_range_error | |
FUNC | napi_get_and_clear_last_exception | |
FUNC | napi_is_exception_pending | |
FUNC | napi_fatal_error | |
FUNC | napi_open_handle_scope | |
FUNC | napi_close_handle_scope | |
FUNC | napi_open_escapable_handle_scope | |
FUNC | napi_close_escapable_handle_scope | |
FUNC | napi_escape_handle | |
FUNC | napi_create_reference | |
FUNC | napi_delete_reference | |
FUNC | napi_reference_ref | |
FUNC | napi_reference_unref | |
FUNC | napi_get_reference_value | |
FUNC | napi_create_array | |
FUNC | napi_create_array_with_length | |
FUNC | napi_create_arraybuffer | |
FUNC | napi_create_external | |
FUNC | napi_create_external_arraybuffer | |
FUNC | napi_create_object | |
FUNC | napi_create_symbol | |
FUNC | napi_create_typedarray | |
FUNC | napi_create_dataview | |
FUNC | napi_create_int32 | |
FUNC | napi_create_uint32 | |
FUNC | napi_create_int64 | |
FUNC | napi_create_double | |
FUNC | napi_create_string_latin1 | |
FUNC | napi_create_string_utf8 | |
FUNC | napi_get_array_length | |
FUNC | napi_get_arraybuffer_info | |
FUNC | napi_get_prototype | |
FUNC | napi_get_typedarray_info | |
FUNC | napi_get_dataview_info | |
FUNC | napi_get_value_bool | |
FUNC | napi_get_value_double | |
FUNC | napi_get_value_external | |
FUNC | napi_get_value_int32 | |
FUNC | napi_get_value_int64 | |
FUNC | napi_get_value_string_latin1 | |
FUNC | napi_get_value_string_utf8 | |
FUNC | napi_get_value_uint32 | |
FUNC | napi_get_boolean | |
FUNC | napi_get_global | |
FUNC | napi_get_null | |
FUNC | napi_get_undefined | |
FUNC | napi_coerce_to_bool | |
FUNC | napi_coerce_to_number | |
FUNC | napi_coerce_to_object | |
FUNC | napi_coerce_to_string | |
FUNC | napi_typeof | |
FUNC | napi_instanceof | |
FUNC | napi_is_array | |
FUNC | napi_is_arraybuffer | |
FUNC | napi_is_typedarray | |
FUNC | napi_is_dataview | |
FUNC | napi_is_date | |
FUNC | napi_strict_equals | |
FUNC | napi_get_property_names | |
FUNC | napi_set_property | |
FUNC | napi_get_property | |
FUNC | napi_has_property | |
FUNC | napi_delete_property | |
FUNC | napi_has_own_property | |
FUNC | napi_set_named_property | |
FUNC | napi_get_named_property | |
FUNC | napi_has_named_property | |
FUNC | napi_set_element | |
FUNC | napi_get_element | |
FUNC | napi_has_element | |
FUNC | napi_delete_element | |
FUNC | napi_define_properties | |
FUNC | napi_call_function | |
FUNC | napi_create_function | |
FUNC | napi_get_cb_info | |
FUNC | napi_get_new_target | |
FUNC | napi_new_instance | |
FUNC | napi_define_class | |
FUNC | napi_wrap | |
FUNC | napi_unwrap | |
FUNC | napi_remove_wrap | |
FUNC | napi_create_async_work | |
FUNC | napi_delete_async_work | |
FUNC | napi_queue_async_work | |
FUNC | napi_cancel_async_work | |
FUNC | napi_get_node_version | |
FUNC | napi_get_version | |
FUNC | napi_create_promise | |
FUNC | napi_resolve_deferred | |
FUNC | napi_reject_deferred | |
FUNC | napi_is_promise | |
FUNC | napi_run_script | |
FUNC | napi_get_uv_event_loop |
Native API接口说明
符号类型 | 符号名 | 备注 |
FUNC | napi_run_script_path | 运行JavaScript文件 |