如何从插件中的单独 C++ 线程调用发射器回调?
How can you call an emitter callback from separate c++ thread in an addon?
对于上下文,我从 问题开始。我需要在另一个线程中调用发射器的回调。我做了一个最小的例子,但它在 emit.Call({cb, result});
上出现段错误我的第一直觉是我对 env
或 emit
函数的生命周期有疑问。
addon.cpp
#include <napi.h>
#include <iostream>
#include <thread>
#include <memory>
#include <functional>
#include <chrono>
std::shared_ptr<std::thread> thread;
bool running = true;
void generate(Napi::Env& env, Napi::Function& emit)
{
while(running)
{
Napi::Array result = Napi::Array::New(env);
for(int i = 0; i < 3; ++i)
{
result[i] = rand()%100;
}
auto cb = Napi::String::New(env, "onFeedData");
emit.Call({cb, result});
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Napi::Value Start(const Napi::CallbackInfo& info)
{
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
auto cb = std::bind(generate, env, emit);
thread = std::make_shared<std::thread>(cb);
return Napi::String::New(env, "OK");
}
Napi::Value Stop(const Napi::CallbackInfo& info)
{
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
running = false;
thread->join();
return Napi::String::New(env, "OK");
}
Napi::Object Init(Napi::Env env, Napi::Object exports)
{
exports.Set(
Napi::String::New(env, "Start"),
Napi::Function::New(env, Start));
exports.Set(Napi::String::New(env, "Stop"),
Napi::Function::New(env, Stop));
return exports;
}
NODE_API_MODULE(addon, Init)
index.js
'use strict'
const EventEmitter = require('events').EventEmitter;
const addon = require('./build/addon.node');
function Main() {
const emitter = new EventEmitter();
emitter.on('onFeedData', (evt) => {
console.log(evt);
})
setTimeout(() => {
addon.Stop( emitter.emit.bind(emitter) );
}, 5000);
addon.Start( emitter.emit.bind(emitter) );
}
Main();
我们可以利用napi_create_threadsafe_function()函数来实现;这种用法在 Whosebug 帖子
中有详细解释
的 node.js 文档
我尝试了很多解决方案,但只有这个有效。
使用napi-thread-safe-callback,您可以安全地从子线程回调:
void example_async_work(const CallbackInfo& info)
{
// Capture callback in main thread
auto callback = std::make_shared<ThreadSafeCallback>(info[0].As<Function>());
bool fail = info.Length() > 1;
// Pass callback to other thread
std::thread([callback, fail]
{
try
{
// Do some work to get a result
if (fail)
throw std::runtime_error("Failure during async work");
std::string result = "foo";
// Call back with result
callback->call([result](Napi::Env env, std::vector<napi_value>& args)
{
// This will run in main thread and needs to construct the
// arguments for the call
args = { env.Undefined(), Napi::String::New(env, result) };
});
}
catch (std::exception& e)
{
// Call back with error
callback->callError(e.what());
}
}).detach();
}
对于上下文,我从 emit.Call({cb, result});
上出现段错误我的第一直觉是我对 env
或 emit
函数的生命周期有疑问。
addon.cpp
#include <napi.h>
#include <iostream>
#include <thread>
#include <memory>
#include <functional>
#include <chrono>
std::shared_ptr<std::thread> thread;
bool running = true;
void generate(Napi::Env& env, Napi::Function& emit)
{
while(running)
{
Napi::Array result = Napi::Array::New(env);
for(int i = 0; i < 3; ++i)
{
result[i] = rand()%100;
}
auto cb = Napi::String::New(env, "onFeedData");
emit.Call({cb, result});
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
Napi::Value Start(const Napi::CallbackInfo& info)
{
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
auto cb = std::bind(generate, env, emit);
thread = std::make_shared<std::thread>(cb);
return Napi::String::New(env, "OK");
}
Napi::Value Stop(const Napi::CallbackInfo& info)
{
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
running = false;
thread->join();
return Napi::String::New(env, "OK");
}
Napi::Object Init(Napi::Env env, Napi::Object exports)
{
exports.Set(
Napi::String::New(env, "Start"),
Napi::Function::New(env, Start));
exports.Set(Napi::String::New(env, "Stop"),
Napi::Function::New(env, Stop));
return exports;
}
NODE_API_MODULE(addon, Init)
index.js
'use strict'
const EventEmitter = require('events').EventEmitter;
const addon = require('./build/addon.node');
function Main() {
const emitter = new EventEmitter();
emitter.on('onFeedData', (evt) => {
console.log(evt);
})
setTimeout(() => {
addon.Stop( emitter.emit.bind(emitter) );
}, 5000);
addon.Start( emitter.emit.bind(emitter) );
}
Main();
我们可以利用napi_create_threadsafe_function()函数来实现;这种用法在 Whosebug 帖子
我尝试了很多解决方案,但只有这个有效。
使用napi-thread-safe-callback,您可以安全地从子线程回调:
void example_async_work(const CallbackInfo& info)
{
// Capture callback in main thread
auto callback = std::make_shared<ThreadSafeCallback>(info[0].As<Function>());
bool fail = info.Length() > 1;
// Pass callback to other thread
std::thread([callback, fail]
{
try
{
// Do some work to get a result
if (fail)
throw std::runtime_error("Failure during async work");
std::string result = "foo";
// Call back with result
callback->call([result](Napi::Env env, std::vector<napi_value>& args)
{
// This will run in main thread and needs to construct the
// arguments for the call
args = { env.Undefined(), Napi::String::New(env, result) };
});
}
catch (std::exception& e)
{
// Call back with error
callback->callError(e.what());
}
}).detach();
}