Bootstrap

HarmonyOS角落里的知识—“Node-API接口实现异步(线程)开发”

 场景

napi_create_async_work是Node-API接口之一,用于创建一个异步工作对象。可以在需要执行耗时操作的场景中使用,以避免阻塞主线程,确保应用程序的性能和响应性能。例如以下场景:

  • 文件操作:读取大型文件或执行复杂的文件操作时,可以使用异步工作对象来避免阻塞主线程。

  • 网络请求:当需要进行网络请求并等待响应时,使用异步工作对象可以确保主线程不被阻塞,从而提高应用程序的响应性能。

  • 数据库操作:当需要执行复杂的数据库查询或写入操作时,使用异步工作对象可以确保主线程不被阻塞,从而提高应用程序的并发性能。

  • 图像处理:当需要对大型图像进行处理或执行复杂的图像算法时,使用异步工作对象可以确保主线程不被阻塞,从而提高应用程序的实时性能。

异步调用支持callback方式和Promise方式,使用哪种方式由应用开发者决定,通过是否传递callback函数进行区分。

使用Promise方式示例

  1. 使用napi_create_async_work创建异步任务,并使用napi_queue_async_work将异步任务加入队列,等待执行。

    struct CallbackData {
        napi_async_work asyncWork = nullptr;
        napi_deferred deferred = nullptr;
        napi_ref callback = nullptr;
        double args = 0;
        double result = 0;
    };
    
    static napi_value AsyncWork(napi_env env, napi_callback_info info)
    {
       size_t argc = 1;
       napi_value args[1];
       napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    
       napi_value promise = nullptr;
       napi_deferred deferred = nullptr;
       napi_create_promise(env, &deferred, &promise);
    
       auto callbackData = new CallbackData();
       callbackData->deferred = deferred;
       napi_get_value_double(env, args[0], &callbackData->args);
    
       napi_value resourceName = nullptr;
       napi_create_string_utf8(env, "AsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
       // 创建异步任务
       napi_create_async_work(env, nullptr, resourceName, ExecuteCB, CompleteCB, callbackData, &callbackData->asyncWork);
       // 将异步任务加入队列
       napi_queue_async_work(env, callbackData->asyncWork);
    
       return promise;
    }
  2. 定义异步任务的第一个回调函数,该函数在工作线程中执行,处理具体的业务逻辑。

    static void ExecuteCB(napi_env env, void *data)
    {
        CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
        callbackData->result = callbackData->args;
    }
  3. 定义异步任务的第二个回调函数,该函数在主线程执行,将结果传递给ArkTS侧。

    static void CompleteCB(napi_env env, napi_status status, void *data)
    {
        CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
        napi_value result = nullptr;
        napi_create_double(env, callbackData->result, &result);
        if (callbackData->result > 0) {
            napi_resolve_deferred(env, callbackData->deferred, result);
        } else {
            napi_reject_deferred(env, callbackData->deferred, result);
        }
    
        napi_delete_async_work(env, callbackData->asyncWork);
        delete callbackData;
    }
  4. 模块初始化以及ArkTS侧调用接口。

    // 模块初始化
    static napi_value Init(napi_env env, napi_value exports)
    {
        napi_property_descriptor desc[] = {
            { "asyncWork", nullptr, AsyncWork, nullptr, nullptr, nullptr, napi_default, nullptr }
        };
        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
        return exports;
    }
    
    // ArkTS侧调用接口
    nativeModule.asyncWork(1024).then((result) => {
        hilog.info(0x0000, 'XXX', 'result is %{public}d', result);
      }
    );

使用callback方式示例

使用示例

  1. 使用napi_create_async_work创建异步任务,并使用napi_queue_async_work将异步任务加入队列,等待执行。

    struct CallbackData {
      napi_async_work asyncWork = nullptr;
      napi_ref callbackRef = nullptr;
      double args[2] = {0};
      double result = 0;
    };
    
    napi_value AsyncWork(napi_env env, napi_callback_info info) 
    {
        size_t argc = 3;
        napi_value args[3];
        napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
        auto asyncContext = new CallbackData();
        // 将接收到的参数保存到callbackData
        napi_get_value_double(env, args[0], &asyncContext->args[0]);
        napi_get_value_double(env, args[1], &asyncContext->args[1]);
        // 将传入的callback转换为napi_ref延长其生命周期,防止被GC掉
        napi_create_reference(env, args[2], 1, &asyncContext->callbackRef);
        napi_value resourceName = nullptr;
        napi_create_string_utf8(env, "asyncWorkCallback", NAPI_AUTO_LENGTH, &resourceName);
        // 创建异步任务
        napi_create_async_work(env, nullptr, resourceName, ExecuteCB, CompleteCB, 
                               asyncContext, &asyncContext->asyncWork); 
        // 将异步任务加入队列
        napi_queue_async_work(env, asyncContext->asyncWork);
        return nullptr;
    }
  2. 定义异步任务的第一个回调函数,该函数在工作线程中执行,处理具体的业务逻辑。

    static void ExecuteCB(napi_env env, void *data) 
    {
        CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
        callbackData->result = callbackData->args[0] + callbackData->args[1];
    }
  3. 定义异步任务的第二个回调函数,该函数在主线程执行,将结果传递给ArkTS侧。

    static void CompleteCB(napi_env env, napi_status status, void *data) 
    {
        CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
        napi_value callbackArg[1] = {nullptr};
        napi_create_double(env, callbackData->result, &callbackArg[0]);
        napi_value callback = nullptr;
        napi_get_reference_value(env, callbackData->callbackRef, &callback);
        // 执行回调函数
        napi_value result;
        napi_value undefined;
        napi_get_undefined(env, &undefined);
        napi_call_function(env, undefined, callback, 1, callbackArg, &result);
        // 删除napi_ref对象以及异步任务
        napi_delete_reference(env, callbackData->callbackRef);
        napi_delete_async_work(env, callbackData->asyncWork);
        delete callbackData;
    }
  4. 模块初始化以及ArkTS侧调用接口。

    // 模块初始化
    static napi_value Init(napi_env env, napi_value exports)
    {
        napi_property_descriptor desc[] = {
            { "asyncWork", nullptr, AsyncWork, nullptr, nullptr, nullptr, napi_default, nullptr }
        };
        napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
        return exports;
    }
    
    // ArkTS侧调用接口
    let num1: number = 123;
    let num2: number = 456;
    nativeModule.asyncWork(num1, num2, (result) => {
      hilog.info(0x0000, 'XXX', 'result is %{public}d', result);
    }); 

    场景

    napi_create_threadsafe_function是Node-API接口之一,用于创建一个线程安全的JavaScript函数。主要用于在多个线程之间共享和调用,而不会出现竞争条件或死锁。例如以下场景:

  5. 异步计算:如果需要进行耗时的计算或IO操作,可以创建一个线程安全的函数,将计算或IO操作放在另一个线程中执行,避免阻塞主线程,提高程序的响应速度。

  6. 数据共享:如果多个线程需要访问同一份数据,可以创建一个线程安全的函数,确保数据的读写操作不会发生竞争条件或死锁等问题。

  7. 在Native入口定义线程安全函数。

    struct CallbackData {
        napi_threadsafe_function tsfn;
        napi_async_work work;
    };
    
    static napi_value StartThread(napi_env env, napi_callback_info info)
    {
        size_t argc = 1;
        napi_value jsCb = nullptr;
        CallbackData *callbackData = nullptr;
        napi_get_cb_info(env, info, &argc, &jsCb, nullptr, reinterpret_cast<void **>(&callbackData));
    
        // 创建一个线程安全函数
        napi_value resourceName = nullptr;
        napi_create_string_utf8(env, "Thread-safe Function Demo", NAPI_AUTO_LENGTH, &resourceName);
        napi_create_threadsafe_function(env, jsCb, nullptr, resourceName, 0, 1, callbackData, nullptr, 
            callbackData, CallJs, &callbackData->tsfn);
    
        // 创建一个异步任务
        napi_create_async_work(env, nullptr, resourceName, ExecuteWork, WorkComplete, callbackData,
            &callbackData->work);
    
        // 将异步任务加入到异步队列中
        napi_queue_async_work(env, callbackData->work);
        return nullptr;
    }
  8. 在工作线程中调用ExecuteWork,并执行线程安全函数。

    static void ExecuteWork(napi_env env, void *data)
    {
        CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
        std::promise<std::string> promise;
        auto future = promise.get_future();
        napi_call_threadsafe_function(callbackData->tsfn, &promise, napi_tsfn_nonblocking);
        try {
            auto result = future.get();
            // OH_LOG_INFO(LOG_APP, "XXX, Result from JS %{public}s", result.c_str());
        } catch (const std::exception &e) {
            // OH_LOG_INFO(LOG_APP, "XXX, Result from JS %{public}s", e.what());
        }
    }
  9. 在JS线程执行异步回调函数。

    static napi_value ResolvedCallback(napi_env env, napi_callback_info info)
    {
        void *data = nullptr;
        size_t argc = 1;
        napi_value argv[1];
        if (napi_get_cb_info(env, info, &argc, argv, nullptr, &data) != napi_ok) {
            return nullptr;
        }
        size_t result = 0;
        char buf[32] = {0};
        napi_get_value_string_utf8(env, argv[0], buf, 32, &result);
        reinterpret_cast<std::promise<std::string> *>(data)->set_value(std::string(buf));
        return nullptr;
    }
    
    static napi_value RejectedCallback(napi_env env, napi_callback_info info)
    {
        void *data = nullptr;
        if (napi_get_cb_info(env, info, nullptr, nullptr, nullptr, &data) != napi_ok) {
            return nullptr;
        }
        reinterpret_cast<std::promise<std::string> *>(data)->set_exception(
            std::make_exception_ptr(std::runtime_error("Error in jsCallback")));
        return nullptr;
    }
    
    static void CallJs(napi_env env, napi_value jsCb, void *context, void *data)
    {
        if (env == nullptr) {
            return;    
        }
        napi_value undefined = nullptr;
        napi_value promise = nullptr;
        napi_get_undefined(env, &undefined);
        napi_call_function(env, undefined, jsCb, 0, nullptr, &promise);
        napi_value thenFunc = nullptr;
        if (napi_get_named_property(env, promise, "then", &thenFunc) != napi_ok) {
            return;
        }
        napi_value resolvedCallback;
        napi_value rejectedCallback;
        napi_create_function(env, "resolvedCallback", NAPI_AUTO_LENGTH, ResolvedCallback, data,
                             &resolvedCallback);
        napi_create_function(env, "rejectedCallback", NAPI_AUTO_LENGTH, RejectedCallback, data,
                             &rejectedCallback);
        napi_value argv[2] = {resolvedCallback, rejectedCallback};
        napi_call_function(env, promise, thenFunc, 2, argv, nullptr);
    }
  10. 任务执行完成后,进行资源清理回收。

    static void WorkComplete(napi_env env, napi_status status, void *data)
    {
        CallbackData *callbackData = reinterpret_cast<CallbackData *>(data);
        napi_release_threadsafe_function(callbackData->tsfn, napi_tsfn_release);
        napi_delete_async_work(env, callbackData->work);
        callbackData->tsfn = nullptr;
        callbackData->work = nullptr;
    }
  11. 模块初始化以及ArkTS侧调用接口。

// 模块初始化
static napi_value Init(napi_env env, napi_value exports) {
    CallbackData *callbackData = new CallbackData(); // 可在线程退出时释放
    napi_property_descriptor desc[] = {
        {"startThread", nullptr, StartThread, nullptr, nullptr, nullptr, napi_default, callbackData},
    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}

// ArkTS侧调用接口
import nativeModule from 'libentry.so'; // 通过import的方式,引入Native能力

let callback = (): Promise<string> => {
  return new Promise((resolve) => {
    setTimeout(() => {
        resolve("string from promise");
      }, 5000);
    });
 }
 nativeModule.startThread(callback);


最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?但是又不知道从哪里下手,而且学习时频繁踩坑,最终浪费大量时间。所以本人整理了一些比较合适的鸿蒙(HarmonyOS NEXT)学习路径和一些资料的整理供小伙伴学习

点击领取→纯血鸿蒙Next全套最新学习资料(安全链接,放心点击

希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

一、鸿蒙(HarmonyOS NEXT)最新学习路线

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)…等技术知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

二、HarmonyOS Next 最新全套视频教程

三、《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

《鸿蒙开发基础》

  • ArkTS语言
  • 安装DevEco Studio
  • 运用你的第一个ArkTS应用
  • ArkUI声明式UI开发
  • .……

《鸿蒙开发进阶》

  • Stage模型入门
  • 网络管理
  • 数据管理
  • 电话服务
  • 分布式应用开发
  • 通知与窗口管理
  • 多媒体技术
  • 安全技能
  • 任务管理
  • WebGL
  • 国际化开发
  • 应用测试
  • DFX面向未来设计
  • 鸿蒙系统移植和裁剪定制
  • ……

《鸿蒙进阶实战》

  • ArkTS实践
  • UIAbility应用
  • 网络案例
  • ……

四、大厂面试必问面试题

五、鸿蒙南向开发技术

六、鸿蒙APP开发必备

七、鸿蒙生态应用开发白皮书V2.0PDF


完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

                        

;