为Node.js编写C/C++模块之【异步回调】

来源:励志前端www.lizhiqianduan.com)

作者:小黑

转载请注明地址http://www.lizhiqianduan.com/myblog/article.php?id=100665

前面的博文中,我介绍了给C++函数传递一个回调的方法,但是那种方式是同步调用的。这篇文章就来教大家如何使用NodeJs提供的libuv库来实现异步回调。

如果你打算学习使用v8引擎,那么你应该详细的看看。如果你想看看本文到底说了些啥,那么,请直接看总结部分。

一、开发前准备


请参考上一篇文章《使用addon为Node.js编写C/C++模块》

然后选一个文件夹,给今天的分享建立一个工程目录,我这里取名为/3_17_addon 

并在工程目录中新建立一个binding.gyp文件,内容暂时为空吧,之后有用。

方便测试,在根目录中新建一个test.js用来测试我们的接口,内容为空就行。

二、异步回调前提


1、至少两个线程。至少得有一个线程来执行我们的耗时操作。

2、有回调函数。耗时操作执行完成之后,需要回调函数通知js。

三、libuv库函数uv_queue_work


异步回调使用libuv库的uv_queue_work()函数就够了,如下是其函数声明。

int uv_queue_work(
    uv_loop_t* loop,
    uv_work_t* req, 
    uv_work_cb work_cb, 
    uv_after_work_cb after_work_cb
)

参数说明:

    loop —  事件循环链表指针,使用v8默认事件循环就行了。

    req —  内部的一个结构体,可以对其附加数据,传递给线程,用于解决多线程共享数据的问题。

    work_cb — 指向线程执行函数的函数指针,这个函数会在单独的线程中执行,原型为 void (*uv_work_cb)(uv_work_t* req)。

    after_work_cb — 指向线程执行完毕的回调函数的函数指针,原型为 void (*uv_after_work_cb)(uv_work_t* req, int status)。

基本上看了这个函数声明之后,我们心中大概也差不多了解了吧。libuv库已经帮我做好了函数封装了,work_cb()中执行线程函数,之后使用after_work_cb()来执行我们的回调函数,还是挺简单的。

四、Addon中异步回调的简单实现


在我们的文件夹/3_17_addon中新建文件lz_async_callback.cc,如下:

// file : lz_async_callback.cc

#include "node.h"
#include "v8.h"
#include "uv.h"

#include <Windows.h>

using namespace v8;


// 线程共享数据的结构体
struct ShareData
{
	uv_work_t request;
	Isolate * isolate;
	Persistent<Function> js_callback;
};

// 耗时操作,2秒
void worker_cb(uv_work_t * req){
	Sleep(2000);
}
// 线程回调
void after_worker_cb(uv_work_t * req,int status){
	ShareData * my_data = static_cast<ShareData *>(req->data);
	Isolate * isolate = my_data->isolate;
	HandleScope scope(isolate);
	Local<Function> js_callback = Local<Function>::New(isolate,my_data->js_callback);
	js_callback->Call(isolate->GetCurrentContext()->Global(),0,NULL);
	delete my_data;
}


void async(const FunctionCallbackInfo<Value> &args){
	Isolate * isolate = args.GetIsolate();
	HandleScope scope(isolate);

	ShareData * req_data = new ShareData;
	req_data->request.data = req_data;
	req_data->isolate = isolate;
	req_data->js_callback.Reset(isolate, Local<Function>::Cast(args[0]));
	uv_queue_work(uv_default_loop(),&(req_data->request),worker_cb,after_worker_cb);
}

void init(Local<Object> exports) {
    NODE_SET_METHOD(exports, "async", async);
}

NODE_MODULE(addon, init)

上面的C代码非常的简单。大致含义就是,异步延时2000毫秒再执行js的回调函数。

编写一个编译文件binding.gyp,如下:

# file : binding.gyp
{
	'targets':[
		{
			'target_name':'lz_async_callback',
			'sources':[
				'lz_async_callback.cc'
			]
		}
	]
}

然后在cmd中,执行我们的编译命令:

> node-gyp configure build --arch=ia32
...
gyp info ok
>

最后,我们再来编写一个测试文件test.js,如下:

// file : test.js

var addon = require("./build/Release/lz_async_callback.node");

addon.async(function(){
	console.log("up up up",Date.now());
});

console.log("lizhiqianduan",Date.now());

接着还是在我们的cmd来执行这个js脚本:

> node test.js
lizhiqianduan 1458202815073
up up up 1458202817061
>

如上的测试例子中,打印字符串“up up up”的时间,恰好比打印字符串“lizhiqianduan”的时间相差2000毫秒左右。我们的异步回调成功了!

五、给我们的线程传递参数


其实上面的例子中,我们已经给线程传递了参数了,就是那个结构体变量。

ShareData * req_data = new ShareData;
req_data->request.data = req_data;
req_data->isolate = isolate;
req_data->js_callback.Reset(isolate, Local<Function>::Cast(args[0]));

这个结构体中,有三个参数,其中两个是我们自定义的,有一个request字段是必须有的,并且它的data指针指向我们的数据。

下面我们来扩展一下结构体的字段,如下

struct ShareData
{
	uv_work_t request;
	Isolate * isolate;
	Persistent<Function> js_callback;
	int i_num;
	char * i_str;
};

我们添加了两个字段,再在初始化的时候赋值:

ShareData * req_data = new ShareData;
req_data->request.data = req_data;
req_data->isolate = isolate;
req_data->js_callback.Reset(isolate, Local<Function>::Cast(args[0]));
req_data->i_num = 123;
req_data->i_str = "lizhiqianduan";

最后,我们在线程回调中,将他们传递给回调函数:

//...
// 线程回调
void after_worker_cb(uv_work_t * req,int status){
	ShareData * my_data = static_cast<ShareData *>(req->data);
	Isolate * isolate = my_data->isolate;
	HandleScope scope(isolate);
	Local<Value> argv[2];
	argv[0] = Integer::New(isolate,my_data->i_num);
	argv[1] = String::NewFromUtf8(isolate,my_data->i_str);
	Local<Function> js_callback = Local<Function>::New(isolate,my_data->js_callback);
	js_callback->Call(isolate->GetCurrentContext()->Global(),2,argv);
	delete my_data;
}
// ...

再修改一下我们的test.js,如下

// file : test.js

var addon = require("./build/Release/lz_async_callback.node");

addon.async(function(i_num,i_str){
	console.log(i_num,i_str,Date.now());
});

console.log("lizhiqianduan",Date.now());

最后再编译运行一下,

> node-gyp configure build
...
node-gyp info ok
> node test.js
lizhiqianduan 1458204330593
123 'lizhiqianduan' 1458204332581

如上,我们就给我们的异步回调函数传递了两个参数了。其他的参数类型也是类似的,就不再重复了。

六、总结


在Nodejs中实现异步回调还是挺简单的,因为Nodejs底层就提供了实现多线程的uv库。而且,使用默认的时间循环,我们可以不必考虑线程之间的的互斥关系,也不用考虑线程的安全问题。

这篇文章主要介绍了如何在C++模块中给Nodejs写异步回调,这里主要介绍如何使用,在具体原理上面,等有空再来详细的讲解。



打赏作者

发表评论

电子邮件地址不会被公开。 必填项已用*标注