1 Dora 传统while/for 事件循环处理框架问题
传统 Dora 节点采用原生的 while/for 循环来同步串行处理事件。**在面对高频传感器输入、多话题输入或长耗时算法场景时,依靠while循环实现响应事件的机制会暴露出实时性差、扩展困难、线程安全隐患大以及生命周期管理混乱等核心技术痛点。**具体核心问题如下:
-
事件 处 理同步阻塞,延 迟 极高 :所有事件处理器同步串行执行,一旦某个核心算法(如图像处理)耗时较长,整个主循环将直接卡死。在此期间新到达的高频事件(如Tick、IMU数据)无法被及时取出,全部堆积在底层队列中,导致系统整体接收延迟从微秒级飙升至长任务的执行时间;
-
while/for循环处理事件的模式输入路由低效且冗余,依赖if/else判断事件ID,当输入事件增多后代码臃肿,查找效率低,且需重复编写事件解析代码;
3 无原生定时器能力,实现定时任务需手动编写守护线程、轮询逻辑,线程安全和生命周期管理完全依赖开发者手写;
5.无标准化的多监听者注册、管理机制,一个事件需触发多个处理逻辑时,要手动维护处理器列表并遍历调用;
- 生命周期与资源无自动管理功能,无法统一的资源回收逻辑,节点异常退出时易产生僵尸线程、资源泄漏,需手动处理线程关闭、资源释放等繁琐操作。
DORA框架中传统for/while 事件处理代码
cpp
#include <iostream>
#include <string>
#include <vector>
#include <chrono>
#include <thread>
#include <cstring>
// Include your template's specific API header
#include "node_api.h"
// Simulate a time-consuming algorithm processing function
void process_image(const char* data, size_t data_len) {
std::cout << "[RawNode] Start processing image, data size: " << data_len << " bytes..." << std::endl;
// Simulate a 200ms processing delay
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "[RawNode] Image processing completed." << std::endl;
}
int main() {
// 1. Initialize the Dora context using your environment-based API
void *dora_context = init_dora_context_from_env();
if (dora_context == nullptr) {
std::cerr << "[RawNode] init dora context failed" << std::endl;
return -1;
}
std::cout << "[RawNode] Dora context initialized. Entering event loop..." << std::endl;
// 2. Traditional blocking infinite while loop
while (1) {
// Fetch the next event via the context pointer
void *event = dora_next_event(dora_context);
if (event == nullptr) {
std::cerr << "[RawNode] ERROR: unexpected end of event" << std::endl;
return -1;
}
// Read the event type using the template's enum scheme
enum DoraEventType ty = read_dora_event_type(event);
// 3. Branching logic using if/else if condition checks
if (ty == DoraEventType_Input) {
char *id;
size_t id_len;
read_dora_input_id(event, &id, &id_len);
char *data;
size_t data_len;
read_dora_input_data(event, &data, &data_len);
// Construct a temporary C++ string for cleaner topic matching
std::string input_id(id, id_len);
// Routing data streams based on the text ID
if (input_id == "image_task") {
// [PAIN POINT]: This synchronous call blocks the entire thread for 200ms.
// Any 'tick' events arriving during this period will buffer up in the queue.
process_image(data, data_len);
}
else if (input_id == "tick") {
std::cout << "[RawNode] Received periodic tick signal." << std::endl;
}
}
else if (ty == DoraEventType_Stop) {
std::cout << "[RawNode] Received stop event (STOP)." << std::endl;
free_dora_event(event); // Prevent memory leaks before escaping
break;
}
else {
std::cerr << "[RawNode] Received unexpected event type: " << ty << std::endl;
free_dora_event(event);
break;
}
// Always release the event structure memory at the end of each iteration
free_dora_event(event);
}
std::cout << "[RawNode] Exited event loop. Cleaning up context..." << std::endl;
// 4. Clean resource teardown
free_dora_context(dora_context);
std::cout << "[RawNode] Finished successfully." << std::endl;
return 0;
}
2 DORA SerialEventLoop框架
SerialEventLoop 框架每个****input_id 对应一个专属线程,同源事件在该线程内严格串行执行 。通过两条核心设计约束,实现架构功能:1.一个 input_id 仅绑定一个 handler------ 无需实现事件多播、handler 去重等复杂逻辑;2.一个 handler 独占一个专属线程 ------ 无需线程池调度,从根源上避免了同源事件的并发问题。两条约束让 SerialEventLoop 避免CCDoraEventLoop 中线程池、Future 追踪、通用 Event 模板等复杂抽象,仅用极少代码实现事件响应核心需求。
2.1 整体架构
SerialEventLoop框架分为两层:SerialWorker ( 执 行 层 )和 SerialEventLoop ( 编 排 层 ) 。其中,SerialWorker 执行层采用了专属线程+独立事件队列的策略;SerialEventLoop采用事件接收 + 分发 + 定时器 + 输出队列的策略。
SerialWorker 执行层核心执行链路是主循环 ──enqueue()──▶ [事件队列] ──condition_variable──▶ [专属线程] ──handler(event)。每个 SerialWorker 实例持有 1 个 std::thread 专属线程、1 个 std::queue<InputEvent> 事件队列和 1 个 std::condition_variable 条件变量。主循环调用 enqueue() 写入事件并发送通知,专属线程被唤醒后从队列取出事件、执行绑定的 handler。实现事件严格按到达顺序依次处理和异源事件完全并行执行的功能
SerialEventLoop 运行在主线程中,负责事件接收与分 发 、定****时 器 线 程管理 、定****时 器 线 程管理 :
- 事件接收与分 发 :阻塞等待 dora_next_event 获取事件,根据 input_id 哈希查表找到对应 SerialWorker,调用 enqueue() 完成事件分发;整个分发过程仅为 O (1) 复杂度的哈希查找,瞬间完成后立即回到等待下一个事件的状态,不会成为性能瓶颈。
- 定 时 器 线 程管理 :启动 1 个独立的 std::thread 线程,按 10ms 粒度轮询已注册的定时器,到期后自动触发绑定的回调函数;支持重复定时器和一次性定时器(触发后自动移除),定时器线程与主事件循环完全解耦,互不影响。
- 定 时 器 线 程管理 :外部 handler 调用 send_output() 时,框架会自动检测当前线程身份:若在主线程且处于事件循环内,直接调用 dora_send_output 发送;否则将输出内容入队,由主循环在每轮迭代开始时批量消费发送。该机制让 handler 无论运行在哪个线程,都能安全、无锁地发送输出。
表1 SerialEventLoop API功能列表
| 功能 | API 功能 | 说明 |
|---|---|---|
| 事件分发 | register_handler(id, handler) | 每个 input_id 绑定一个专属线程 handler |
| 定时器 | register_timer(id, interval, cb, repeat) | 重复 / 一次性,10ms 精度 |
| 取消定时器 | cancel_timer(id) | 线程安全 |
| 输出发送 | send_output(id, data) | 自动路由(直接发送 / 入队) |
2.2 示例代码
事件注册测试代码
cpp
#include "SerialEventLoop.hpp"
#include <chrono>
using namespace dora_extensions;
// Test Scenario 1: Simulate a time-consuming image recognition algorithm (200ms)
void on_image_received(const InputEvent& event) {
std::cout << "[Worker Thread A] ===> START: Processing heavy image task. Size: "
<< event.data.size() << " bytes." << std::endl;
// Simulate complex OpenCV or deep learning inference execution time
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::cout << "[Worker Thread A] <=== END: Image processing done." << std::endl;
}
// Test Scenario 2: Simulate an ultra-lightweight, high-frequency/periodic control command processing (0ms)
void on_tick_received(const InputEvent& event) {
// Safely convert the binary byte stream back to a string for printing (assuming text format)
std::string msg(reinterpret_cast<const char*>(event.data.data()), event.data.size());
std::cout << "[Worker Thread B] Fast reply for topic [" << event.id
<< "] -> Value: " << msg << std::endl;
}
int main() {
// 1. Create an instance of the event loop, named "my_test_node"
SerialEventLoop loop("my_test_node");
// 2. Register the binding relationship between topics and callback functions
// When "image_task" is triggered, data is automatically dispatched to Worker Thread A's private queue
loop.register_handler("image_task", on_image_received);
// When "tick" is triggered, data is automatically dispatched to Worker Thread B's private queue
loop.register_handler("tick", on_tick_received);
// 3. Block and run. The main thread takes over the native C API polling of the Dora runtime
loop.run();
return 0;
}
SerialEventLoop.hpp 源代码
cpp
#include "SerialEventLoop.hpp"
#include <cassert>
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
using namespace dora_extensions;
// ============================================================
// 测试1: SerialWorker 独立单元测试
// ============================================================
void test_serial_worker_basic() {
std::cout << "[测试1] SerialWorker 基本功能..." << std::endl;
std::atomic<int> call_count{0};
std::vector<std::string> received_ids;
{
SerialWorker worker("test_worker",
[&](const InputEvent& e) {
call_count++;
received_ids.push_back(e.id);
});
// 入队几个事件
for (int i = 0; i < 5; i++) {
InputEvent ie;
ie.id = "input_" + std::to_string(i);
ie.data = {uint8_t(i), uint8_t(i + 1), uint8_t(i + 2)};
worker.enqueue(ie);
}
// 等待worker处理完(给一点时间)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} // worker析构,自动stop
assert(call_count == 5);
assert(received_ids.size() == 5);
for (int i = 0; i < 5; i++) {
assert(received_ids[i] == "input_" + std::to_string(i));
}
std::cout << "[测试1] 通过 ✓" << std::endl;
}
// ============================================================
// 测试2: SerialWorker 顺序性测试
// ============================================================
void test_serial_worker_ordering() {
std::cout << "[测试2] SerialWorker 事件顺序性..." << std::endl;
std::vector<int> processed;
std::mutex mtx;
SerialWorker worker("order_worker",
[&](const InputEvent& e) {
std::lock_guard<std::mutex> lock(mtx);
processed.push_back(e.data[0]);
});
// 按顺序入队100个事件
for (int i = 0; i < 100; i++) {
InputEvent ie;
ie.id = "seq";
ie.data = {uint8_t(i)};
worker.enqueue(ie);
}
// 等待全部处理完
std::this_thread::sleep_for(std::chrono::milliseconds(200));
assert(processed.size() == 100);
for (int i = 0; i < 100; i++) {
assert(processed[i] == i); // 必须严格保序
}
std::cout << "[测试2] 通过 ✓ (处理了 " << processed.size() << " 个事件)" << std::endl;
}
// ============================================================
// 测试3: SerialEventLoop 注册与取消
// ============================================================
void test_event_loop_registration() {
std::cout << "[测试3] EventLoop 注册/取消..." << std::endl;
SerialEventLoop loop("test_node");
// 注册handler
loop.register_handler("camera", [](const InputEvent&) {});
loop.register_handler("lidar", [](const InputEvent&) {});
// 注册定时器
int tick_count = 0;
loop.register_timer("tick", std::chrono::milliseconds(50),
[&]() { tick_count++; }, true);
// 等待定时器触发几次
std::this_thread::sleep_for(std::chrono::milliseconds(120));
assert(tick_count >= 2);
// 取消定时器
bool ok = loop.cancel_timer("tick");
assert(ok);
int snapshot = tick_count;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
assert(tick_count == snapshot); // 取消后不应再触发
// 取消失败的定时器
ok = loop.cancel_timer("nonexistent");
assert(!ok);
std::cout << "[测试3] 通过 ✓ (tick_count=" << tick_count << ")" << std::endl;
}
// ============================================================
// 测试4: 一次性定时器
// ============================================================
void test_oneshot_timer() {
std::cout << "[测试4] 一次性定时器..." << std::endl;
SerialEventLoop loop("test_node");
std::atomic<int> fired{0};
loop.register_timer("once", std::chrono::milliseconds(30),
[&]() { fired++; }, false); // repeat=false
std::this_thread::sleep_for(std::chrono::milliseconds(80));
// 应该在30ms左右触发一次,之后不再触发
assert(fired == 1);
std::this_thread::sleep_for(std::chrono::milliseconds(80));
assert(fired == 1); // 仍然只有一次
std::cout << "[测试4] 通过 ✓" << std::endl;
}
// ============================================================
// 测试5: send_output 线程安全
// ============================================================
void test_send_output() {
std::cout << "[测试5] send_output 入队..." << std::endl;
SerialEventLoop loop("test_node");
std::atomic<int> sent{0};
// 模拟从多个线程调用 send_output
std::thread t1([&]() {
for (int i = 0; i < 50; i++) {
loop.send_output("out_a", {uint8_t(i)});
sent++;
}
});
std::thread t2([&]() {
for (int i = 0; i < 50; i++) {
loop.send_output("out_b", {uint8_t(i)});
sent++;
}
});
t1.join();
t2.join();
assert(sent == 100);
// 注意: 这个测试不运行 run(),所以消息还没真正发送到Dora
// 但它们已被安全地放入队列
std::cout << "[测试5] 通过 ✓ (入队了 " << sent << " 条消息)" << std::endl;
}
// ============================================================
// 测试6: timer回调中调用 send_output
// ============================================================
void test_timer_send_output() {
std::cout << "[测试6] 定时器回调中发送输出..." << std::endl;
SerialEventLoop loop("test_node");
std::atomic<int> output_calls{0};
// 在timer回调中调用send_output(timer线程非主线程,会入队)
loop.register_timer("sender", std::chrono::milliseconds(30),
[&]() {
loop.send_output("timer_out", {0xAA, 0xBB});
output_calls++;
}, true);
std::this_thread::sleep_for(std::chrono::milliseconds(80));
assert(output_calls >= 2);
loop.cancel_timer("sender");
std::cout << "[测试6] 通过 ✓ (send_output调用了 " << output_calls << " 次)" << std::endl;
}
int main() {
std::cout << "========================================" << std::endl;
std::cout << " SerialEventLoop 单元测试" << std::endl;
std::cout << "========================================" << std::endl;
test_serial_worker_basic();
test_serial_worker_ordering();
test_event_loop_registration();
test_oneshot_timer();
test_send_output();
test_timer_send_output();
std::cout << "========================================" << std::endl;
std::cout << " 全部测试通过 ✓" << std::endl;
std::cout << "========================================" << std::endl;
return 0;
}
综上所述,SerialEventLoop的优势,相比于while 循环方式的事件处理框架仅提供了「等事件→处理事件」的最小集;而 SerialEventLoop 代码框架升级为「不同输入源自动隔离到专属线程 + 原生定时器 + 线程安全输出」的完整节点运行时,开发者仅需编写每个 handler 的业务逻辑即可。