一、Node-API异步任务机制概述
Node-API异步任务开发主要用于执行耗时操作的场景中使用,以避免阻塞主线程,确保应用程序的性能和响应效率。
1、应用场景:
- 文件操作:读取大型文件或执行复杂的文件操作时,可以使用异步工作项来避免阻塞主线程。
- 网络请求:当需要进行网络请求并等待响应时,可以使用异步工作项来避免阻塞主线程,从而提高应用程序的响应性能。
- 数据库操作:当需要执行复杂的数据库查询或写入操作时,可以使用异步工作项来避免阻塞主线程,从而提高应用程序的并发性能。
- 图形处理:当需要对大型图像进行处理或执行复杂的图像算法时,可以使用异步工作项来避免阻塞主线程,从而提高应用程序的实时性能。
2、 异步方式与同步方式的区别
异步方式与同步方式的区别在于,同步方式中所有代码的处理都在ArkTS主线程中完成,而异步方式中的所有代码在多线程中完成。Node-API主要是通过创建一个异步工作项来实现异步任务开发。
3、异步任务开发总体步骤
- 在Native接口函数中,创建一个异步工作项,并置入libuv调度队列中,然后立即返回一个临时结果给ArkTS调用者;
- 通过libuv线程池创建并调度work子线程完成异步业务逻辑的执行;
- 通过Callback回调或者Promise延时对象返回真正的处理结果,并用于应用侧UI刷新。
4、异步任务流程原理
异步工作项的底层机制是基于libuv异步库来实现的,具体流程原理如下:
依赖Node-API提供的napi_create_async_work接口创建异步工作项:
NAPI_EXTERN 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:传入接口调用者的环境,包含方舟引擎等。由框架提供,默认情况下直接传入即可。
[in] async_resource:可选项,关联async_hooks。
[in] async_resource_name:异步资源标识符,主要用于async_hooks API暴露断言诊断信息。
[in] execute:执行业务逻辑计算函数,由libuv线程池调度执行。在该函数中执行IO、CPU密集型任务,不阻塞主线程。
[in] complete:execute回调函数执行完成或取消后,触发执行该函数。此函数在EventLoop子线程中执行。
[in] data:用户提供的上下文数据,用于传递数据。
[out] result:napi_async_work*指针,用于返回当前此处函数调用创建的异步工作项。 返回值:返回napi_ok表示转换成功,其他值失败。
5、Execute回调
- execute函数用于执行工作项的业务逻辑,异步工作项被调度后,该函数从上下文数据中获取输入数据,在work子线程中完成业务逻辑计算(不阻塞主线程)并将结果写入上下文数据。
- 因为execute函数不在ArkTS线程中,所以不允许execute函数调用napi的接口。业务逻辑的返回值可以返回到complete回调中处理。
6、Complete回调
- 业务逻辑处理execute函数执行完成或被取消后,通过事件通知EventLoop执行complete函数,complete函数从上下文数据中获取结果,转换为napi_value类型,调用ArkTS回调函数或通过Promise resolve()返回结果。
- 该函数运行在ArkTS主线程下,因此可以调用napi的接口,将execute中的返回值封装成ArkTS对象返回。
7、两种异步模型
Node-API异步接口实现支持Callback方式和Promise方式,具体使用哪种方式由应用开发者决定,通过是否传递callback函数进行区分。
(1)Callback异步模型
- 用户在调用Native接口的时候,Native接口将异步执行任务,并临时返回空值给ArkTS应用侧。
- 异步任务执行结果以参数的形式提供给用户注册的ArkTS回调函数,并通过napi_call_function将ArkTS回调函数进行调用执行以反馈结果到ArkTS应用侧。
(2)Promise异步模型
- 用户在调用Native接口的时候,Native接口将异步执行任务,并返回一个Promise对象给ArkTS应用侧。
- Promise对象提供了API使得异步执行可以按照同步的流程表示出来,避免了层层嵌套的回调引用。
- 异步任务执行结果以参数的形式提供给与ArkTS应用侧Promise对象关联的deferred对象,并通过napi_resolve_deferred将计算结果反馈到ArkTS应用侧。
二、异步任务开发时序交互图
1、Callback异步开发时序交互
2、Promise异步开发时序交互
三、异步任务开发步骤(Callback简介)
四、异步任务开发步骤(实例)
1、Callback异步开发步骤
(1)ArkTS应用侧开发
//Index.ets文件
import testNapi from 'libentry.so';
import Constants from '../../common/constants/CommonConstants';
@Entry
@Component
struct Index {
@State imagePath: string = Constants.INIT_IMAGE_PATH;
imageName: string = '';
build() {
Column() {
...
// button list, prompting the user to click the button to select the target image.
Column() {
...
// multi-threads callback async button
Button($r('app.string.async_callback_button_title'))
.width(Constants.FULL_PARENT)
.margin($r('app.float.button_common_margin'))
.onClick(() => {
this.imageName = Constants.CALLBACK_BUTTON_IMAGE;
testNapi.getImagePathAsyncCallBack(this.imageName, (result: string) => {
this.imagePath = Constants.IMAGE_ROOT_PATH + result;
});
})
...
}
...
}
...
}
}
(2)Native侧开发
导出Native接口:将Native接口导出到ArkTS侧,用于支撑ArkTS对象调用和模块编译构建。
// index.d.ts文件
export const getImagePathAsyncCallBack: (imageName: string, callBack: (result: string) => void) => void;
execute回调:定义异步工作项的第一个回调函数,该函数在work子线程中执行,处理具体的业务逻辑。
// MultiThreads.cpp文件
static void ExecuteFunc([[maybe_unused]] napi_env env, void *data) {
// create producer thread
thread producer(ProductElement, data);
// the producer and consumer threads must be synchronized
// otherwise, the complete operation is triggered to communicate with the ArkTS after the executeFunc is complete
// the result is unpredictable
producer.join();
// create consumer thread
thread consumer(ConsumeElement, data);
consumer.join();
}
complete回调:定义异步工作项的第二个回调函数,该函数在ArkTS主线程中执行,将结果传递给ArkTS侧。
// MultiThreads.cpp文件
static void CompleteFuncCallBack(napi_env env, [[maybe_unused]] napi_status status, void *data) {
// parse context data
ContextData *contextData = static_cast<ContextData *>(data);
napi_value callBack = nullptr;
napi_status operStatus = napi_get_reference_value(env, contextData->callbackRef, &callBack);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return;
}
// define the undefined variable, which is used in napi_call_function
// because no other data is transferred, the variable is defined as undefined
napi_value undefined = nullptr;
operStatus = napi_get_undefined(env, &undefined);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return;
}
// convert the calculation result of C++ sub-thread to the napi_value type
napi_value callBackArgs = nullptr;
operStatus = napi_create_string_utf8(env, contextData->result.c_str(),
contextData->result.length(), &callBackArgs);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return;
}
// call the JS callback and send the async calculation result on the Native to ArkTS application
napi_value callBackResult = nullptr;
(void)napi_call_function(env, undefined, callBack, 1, &callBackArgs, &callBackResult);
// destroy data and release memory
DeleteContext(env, contextData);
}
Native异步任务开发接口:解析ArkTS应用侧参数,使用napi_create_async_work创建异步工作项,并使用napi_queue_async_work将异步任务加入队列,等待调度执行。
// MultiThreads.cpp文件
// callback async interface
static napi_value GetImagePathAsyncCallBack(napi_env env, napi_callback_info info) {
size_t paraNum = 2;
napi_value paraArray[2] = {nullptr};
// parse parameters
napi_status operStatus = napi_get_cb_info(env, info, ¶Num, paraArray, nullptr, nullptr);
if (operStatus != napi_ok) {
return nullptr;
}
napi_valuetype paraDataType = napi_undefined;
operStatus = napi_typeof(env, paraArray[0], ¶DataType);
if ((operStatus != napi_ok) || (paraDataType != napi_string)) {
return nullptr;
}
operStatus = napi_typeof(env, paraArray[1], ¶DataType);
if ((operStatus != napi_ok) || (paraDataType != napi_function)) {
return nullptr;
}
// napi_value convert to char *
constexpr size_t buffSize = 100;
char strBuff[buffSize]{}; // char buffer for imageName string
size_t strLength = 0;
operStatus = napi_get_value_string_utf8(env, paraArray[0], strBuff, buffSize, &strLength);
if ((operStatus != napi_ok) || (strLength == 0)) {
return nullptr;
}
// defines context data. the memory will be released in CompleteFunc
auto contextData = new ContextData;
contextData->args = strBuff;
operStatus = napi_create_reference(env, paraArray[1], 1, &contextData->callbackRef);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
// async resource
napi_value asyncName = nullptr;
string asyncStr = "async callback";
operStatus = napi_create_string_utf8(env, asyncStr.c_str(), asyncStr.length(), &asyncName);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
// create async work
operStatus = napi_create_async_work(env, nullptr, asyncName, ExecuteFunc, CompleteFuncCallBack,
static_cast<void *>(contextData), &contextData->asyncWork);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
// add the async work to the queue and wait for scheduling
operStatus = napi_queue_async_work(env, contextData->asyncWork);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
}
return nullptr;
}
2、Promise异步开发步骤
(1)ArkTS应用侧开发
// index.d.ts文件
import testNapi from 'libentry.so';
import Constants from '../../common/constants/CommonConstants';
@Entry
@Component
struct Index {
@State imagePath: string = Constants.INIT_IMAGE_PATH;
imageName: string = '';
build() {
Column() {
...
// button list, prompting the user to click the button to select the target image.
Column() {
...
// multi-threads promise async button
Button($r('app.string.async_promise_button_title'))
.width(Constants.FULL_PARENT)
.margin($r('app.float.button_common_margin'))
.onClick(() => {
this.imageName = Constants.PROMISE_BUTTON_IMAGE;
let promiseObj = testNapi.getImagePathAsyncPromise(this.imageName);
promiseObj.then((result: string) => {
this.imagePath = Constants.IMAGE_ROOT_PATH + result;
})
})
...
}
...
}
...
}
}import testNapi from 'libentry.so';
import Constants from '../../common/constants/CommonConstants';
@Entry
@Component
struct Index {
@State imagePath: string = Constants.INIT_IMAGE_PATH;
imageName: string = '';
build() {
Column() {
...
// button list, prompting the user to click the button to select the target image.
Column() {
...
// multi-threads promise async button
Button($r('app.string.async_promise_button_title'))
.width(Constants.FULL_PARENT)
.margin($r('app.float.button_common_margin'))
.onClick(() => {
this.imageName = Constants.PROMISE_BUTTON_IMAGE;
let promiseObj = testNapi.getImagePathAsyncPromise(this.imageName);
promiseObj.then((result: string) => {
this.imagePath = Constants.IMAGE_ROOT_PATH + result;
})
})
...
}
...
}
...
}
}
(2)Native侧开发
导出Native接口:将Native接口导出到ArkTS侧,用于支撑ArkTS对象调用和模块编译构建。
// index.d.ts文件
export const getImagePathAsyncPromise: (imageName: string) => Promise<string>;
execute回调:定义异步工作项的第一个回调函数,该函数在work子线程中执行,处理具体的业务逻辑。
// MultiThreads.cpp文件
static void ExecuteFunc([[maybe_unused]] napi_env env, void *data) {
// create producer thread
thread producer(ProductElement, data);
// the producer and consumer threads must be synchronized
// otherwise, the complete operation is triggered to communicate with the ArkTS after the executeFunc is complete
// the result is unpredictable
producer.join();
// create consumer thread
thread consumer(ConsumeElement, data);
consumer.join();
}
complete回调:定义异步工作项的第二个回调函数,该函数在ArkTS主线程中执行,将结果传递给ArkTS侧。
// MultiThreads.cpp文件
static void CompleteFuncPromise(napi_env env, [[maybe_unused]] napi_status status, void *data) {
// parse context data
ContextData *contextData = static_cast<ContextData *>(data);
// convert the calculation result of C++ sub-thread to the napi_value type
napi_value promiseArgs = nullptr;
napi_status operStatus =
napi_create_string_utf8(env, contextData->result.c_str(), contextData->result.length(), &promiseArgs);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return;
}
// the deferred and promise object are associated. the result is sent to ArkTS application through this interface
operStatus = napi_resolve_deferred(env, contextData->deferred, promiseArgs);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return;
}
// destroy data and release memory
DeleteContext(env, contextData);
}
Native异步任务开发接口:解析ArkTS应用侧参数,使用napi_create_async_work创建异步工作项,并使用napi_queue_async_work将异步任务加入队列,等待调度执行。
// MultiThreads.cpp文件
// promise async interface
static napi_value GetImagePathAsyncPromise(napi_env env, napi_callback_info info) {
size_t paraNum = 1;
napi_value paraArray[1] = {nullptr};
// parse parameters
napi_status operStatus = napi_get_cb_info(env, info, ¶Num, paraArray, nullptr, nullptr);
if (operStatus != napi_ok) {
return nullptr;
}
napi_valuetype paraDataType = napi_undefined;
operStatus = napi_typeof(env, paraArray[0], ¶DataType);
if ((operStatus != napi_ok) || (paraDataType != napi_string)) {
return nullptr;
}
// napi_value convert to char *
constexpr size_t buffSize = 100;
char strBuff[buffSize]{}; // char buffer for imageName string
size_t strLength = 0;
operStatus = napi_get_value_string_utf8(env, paraArray[0], strBuff, buffSize, &strLength);
if ((operStatus != napi_ok) || (strLength == 0)) {
return nullptr;
}
// defines context data. the memory will be released in CompleteFunc
auto contextData = new ContextData;
contextData->args = strBuff;
// async resource
napi_value asyncName = nullptr;
string asyncStr = "async promise";
operStatus = napi_create_string_utf8(env, asyncStr.c_str(), asyncStr.length(), &asyncName);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
// create async work
operStatus = napi_create_async_work(env, nullptr, asyncName, ExecuteFunc, CompleteFuncPromise,
static_cast<void *>(contextData), &contextData->asyncWork);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
// add the async work to the queue and wait for scheduling
operStatus = napi_queue_async_work(env, contextData->asyncWork);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
// create promise object
napi_value promiseObj = nullptr;
operStatus = napi_create_promise(env, &contextData->deferred, &promiseObj);
if (operStatus != napi_ok) {
DeleteContext(env, contextData);
return nullptr;
}
return promiseObj;
}