侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

NAPI 笔记 10:异步 - AsyncWorker

barwe
2022-09-01 / 0 评论 / 0 点赞 / 2,488 阅读 / 4,215 字
温馨提示:
本文最后更新于 2023-06-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

为了与 Node.js 的 事件循环 机制一致,如果原生模块中涉及到耗时操作,它也不应该阻塞事件循环主线程(即执行 JavaScript 代码的线程)的执行。这意味着原生模块需要借助于 libuv 的部分功能实现自己的 异步(Asynchronous),这样就不必等到耗时的原生函数执行完成才返回结果。

node-addon-api 提供了AsyncWorker抽象类实现异步操作,这基本上可以覆盖掉大部分需要异步的场景。该类通过对数据在事件循环线程和工作线程之间移动的抽象,来帮助开发者管理异步操作。

如果AsyncWorker不能解决问题,node-addon-api 还提供了AsyncContextCallbakScope来确保异步操作能够被运行环境很好的追踪。

class AsyncWorker

这是一个抽象类,开发者应该继承这个类,并至少实现其Execute()方法。

class EchoWorker : public AsyncWorker {
    public:
    EchoWorker(Function& callback, std::string& echo)
        : AsyncWorker(callback), echo(echo) {}
    ~EchoWorker() {}

    void Execute() override {
        /* cpu heavy task */
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }

    void OnOK() override {
        HandleScope scope(Env());
        Callback().Call({
            Env().Null(),
            String::New(Env(), echo)
        });
    }

    private:
    std::string echo;
}

实例化一个自定义的 EchoWorker 对象需要两个参数:

  • Function& 回调函数,在任务函数执行成功之后由事件循环主线程执行
  • 一个或者多个自定义数据

其中回调函数传递给父类 AsyncWorker 的构造函数,AsyncWorker 的构造函数有:

explicit AsyncWorker(const Function& callback);
explicit AsyncWorker(const Function& callback, const char* resource_name);
explicit AsyncWorker(const Function& callback, const char* resource_name, const Object& resource);
explicit AsyncWorker(const Object& receiver, const Function& callback);
explicit AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name);
explicit AsyncWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource);
explicit AsyncWorker(Env env);
explicit AsyncWorker(Env env, const char* resource_name);
explicit AsyncWorker(Env env, const char* resource_name, const Object& resource);
  • callback: 异步任务完成之后由 JavaScript 主线程执行的回调函数
  • resource_name: async_hooks API 暴露的用于诊断的资源类型标识符(不知道具体干嘛的)
  • resource: 与异步操作相关、需要被传递给 async_hooks 的对象
  • receiver: 传递给被调函数的 this 对象(谁的 this?)

构造 AsyncWorker 的一个关键参数是回调函数,该函数将在异步任务执行完成之后被放入任务队列中,在事件循环主线程上运行。

扩展 AsyncWorker 类需要复写其 Execute 方法,该方法是实例化子类时自动执行的任务,一般是会阻塞主线程的任务,所以才需要异步执行。

getters

Env()

Receiver()

Callback()

FunctionReference& AsyncWorker::Callback();

创建异步任务时的回调函数的函数对象的强引用。

FunctionReference 对象与 Function 对象类似,通过执行其Call()方法可以执行回调函数。

AsyncWorker 会将异步任务(Execute 方法)的返回结果作为参数传递给回调函数。

所以执行回调函数的代码看起来像:

Callback().Call(<异步任务的返回结果>)

actions

Queue()

开始排队执行任务。

Cancel()

如果排队的任务尚未开始执行,取消它。已经开始运行的任务不能被取消。如果取消成功,OnOK 和 OnError 方法都不会被调用。

SetError()

void AsyncWorker::SetError(string& msg);

在执行异步任务的过程中随时可以通过此方法将任务状态标记为 失败

一旦调用此方法,异步任务将停止执行,并调用 OnError 方法。

SuppressDestruct()

防止异步任务完成、OnOK 方法被调用后解构 AsyncWorker 对象。

Destroy()

如果没有调用 SuppressDestruct 方法,在 OnOK 或者 OnError 方法被调用后会立即调用此方法解构实例。

默认使用 delete 删除实例,可以保证通过除了 new 之外的方法构建的实例也能被正确删除。

overrides

Execute()

virtual void AsyncWorker::Execute() = 0;

使用 libuv 提供的 工作线程事件循环线程 外执行一些耗时任务。

扩展 AsyncWorker 的子类必须实现这一方法,这个方法是异步任务的任务主体。

因为该任务是在主线程之外执行,所以不能使用任何 node-addon-api 提供的 API,不能执行任何可能调用 JavaScript 的代码。

所有需要调用 node-addon-api 的 API 和执行 JavaScript 的代码都应该放到 OnOK 和 OnError 方法中。

OnOK 和 OnError 方法在异步任务完成之后,由主线程执行。

GetResult()

virtual vector<napi_value> AsyncWorker::GetResult(Env env);

此方法用来定义执行回调函数时需要传入的参数,回调函数默认以无参方法执行,有参执行时需要复写此方法。

OnOK()

virtual void AsyncWorker::OnOK();

OnOK 方法在 Execute 方法正常执行完之后由主线程执行,所以可以使用 node-addon-api 和调用 JavaScript 的代码。

AsyncWorker 提供默认实现:执行不带参数的回调函数,需要带参数可以直接复写此方法或者复写 GetResult 方法。

OnError()

virtual void AsyncWorker::OnError(Error& error);

OnError 方法在 Execute 方法执行出错之后立即由主线程执行,所以也可以使用 node-addon-api 和调用 JavaScript 的代码。

AsyncWorker 提供默认实现:执行回调函数,将错误对象作为第一个参数传递给回调函数。

以出错的方法结束 Execute 方法的方式有两种:

  • 启用 C++ 异常时,使用 throw 抛出一个异常
  • 使用 SetError 设置错误

OnWorkComplete()

virtual void AsyncWorker::OnWorkComplete(Env env, napi_status status);

在 JavaScript 工作线程执行完任务之后,此方法会检测任务的执行状态,根据任务状态调用 OnOK 或者 OnError 并传递任务结果。

如果任务被成功取消,此方法不会被调用,所以 OnOK 和 OnError 方法也不会被调用。

当结果被传递出去(开始执行 OnOK 或者 OnError)之后,如果没有调用 SuppressDestruct 方法,AsyncWorker 对象将会被解构。

OnExecute()

virtual void OnExecute(Env env);

这个方法在异步任务被分配到工作线程时立即被调用,它的作用就是调用 Execute 方法开始在工作线程中执行任务。

此方法虽然接受一个 Env 对象作为参数,但是在其内部不能使用任何 node-addon-api 和包含调用 JavaScript 的代码,因为它是在工作线程中执行的。

how to use

执行 addon 中的一个异步方法,与执行普通的异步函数类似。例如

setTimeout(() => { /* ... */ }, 200)

我们会传递一个 回调函数 给原生方法,也可以传递其他参数给原生方法,假设我们传递了一个额外的字符串:

exampleAddon.echo("hello c++", v => console.log(v))

在 C++ 中我们定义了一个 Echo 函数,并将其导出为 echo:

#include <napi.h>
using namespace std;
using namespace Napi;

void Echo(const CallbackInfo& info) {
    string msg = info[0].as<String>();
    Function cb = info[1].as<Function>();
    EchoWorker* wk = new EchoWorker(cb, msg);
    wk->Queue();
}

实例化一个对象,调用其 Queue 方法即可。

reference

0

评论区