Qt实现事件发布订阅系统

基于Qt的MetaCall事件实现的线程安全事件发布订阅系统,类似ROS的消息订阅模式。

特性

  • 线程安全:使用互斥锁保护订阅者列表,支持多线程环境
  • 多种发布模式:支持同步、异步当前线程、异步接收者三种发布模式
  • 灵活的回调:支持lambda表达式和函数对象作为回调
  • 自动管理:基于Qt的事件循环,自动处理线程间通信
  • 类型安全:使用QVariant传递数据,支持任意类型
  • 单例模式:全局唯一实例,方便使用

架构设计

代码

eventpubsub.h
cpp 复制代码
#ifndef EVENTPUBSUB_H
#define EVENTPUBSUB_H

#include <QObject>
#include <QString>
#include <QMap>
#include <QList>
#include <QMutex>
#include <QVariant>
#include <QThread>
#include <QEvent>
#include <functional>
#include <memory>

/**
 * @brief 发布类型枚举
 */
enum class PublishType {
    Sync,              // 同步:在当前线程立即执行
    AsyncCurrent,      // 异步当前线程:在当前线程的事件循环中执行
    AsyncReceiver      // 异步接收者:在接收者对象所在线程的事件循环中执行
};

/**
 * @brief 订阅者信息结构体
 */
struct SubscriberInfo {
    QObject *receiver;                    // 订阅者对象指针
    std::function<void(const QVariant &)> callback;  // 回调函数(lambda或函数对象)
    QThread *receiverThread;               // 接收者所在线程(用于异步接收者模式)
    
    SubscriberInfo() : receiver(nullptr), receiverThread(nullptr) {}
    
    SubscriberInfo(QObject *recv, std::function<void(const QVariant &)> cb)
        : receiver(recv), callback(cb), receiverThread(recv ? recv->thread() : nullptr) {}
    
    // 用于比较和查找
    bool operator==(const SubscriberInfo &other) const {
        return receiver == other.receiver && 
               receiverThread == other.receiverThread;
    }
};
/**
 * @brief MetaCall事件类,用于在事件循环中执行回调
 */
class CallbackEvent : public QEvent {
    public:
        static QEvent::Type EventType;
        
        CallbackEvent(std::function<void(const QVariant &)> cb, const QVariant &data)
            : QEvent(EventType), m_callback(cb), m_data(data) {}
        
        void execute() {
            if (m_callback) {
                m_callback(m_data);
            }
        }
        
    private:
        std::function<void(const QVariant &)> m_callback;
        QVariant m_data;
    };
    
    /**
     * @brief 接收者辅助对象,用于在接收者线程中处理回调事件
     */
    class ReceiverHelper : public QObject {
        Q_OBJECT
    public:
        explicit ReceiverHelper(QObject *parent = nullptr) : QObject(parent) {}
        
    protected:
        bool event(QEvent *e) override {
            if (e->type() == CallbackEvent::EventType) {
                CallbackEvent *callbackEvent = static_cast<CallbackEvent *>(e);
                callbackEvent->execute();
                return true;
            }
            return QObject::event(e);
        }
    };
    
/**
 * @brief 事件发布订阅系统
 * 
 * 基于Qt的MetaCall事件实现的线程安全事件发布订阅系统
 * 支持同步和异步两种发布模式
 */
class EventPubSub : public QObject {
    Q_OBJECT
    
public:
    /**
     * @brief 获取单例实例
     */
    static EventPubSub* instance();
    
    /**
     * @brief 订阅事件
     * @param topicId 主题ID(消息ID)
     * @param receiver 订阅者对象指针
     * @param callback 回调函数(lambda或函数对象)
     * @return 是否订阅成功
     */
    bool subscribe(const QString &topicId, 
                   QObject *receiver, 
                   std::function<void(const QVariant &)> callback);
    
    /**
     * @brief 取消订阅
     * @param topicId 主题ID
     * @param receiver 订阅者对象指针
     * @return 是否取消成功
     */
    bool unsubscribe(const QString &topicId, QObject *receiver);
    
    /**
     * @brief 取消某个对象的所有订阅
     * @param receiver 订阅者对象指针
     * @return 取消的订阅数量
     */
    int unsubscribeAll(QObject *receiver);
    
    /**
     * @brief 发布消息
     * @param topicId 主题ID
     * @param data 消息数据(QVariant类型,可以包含任意类型)
     * @param type 发布类型(默认异步接收者)
     */
    void publish(const QString &topicId, 
                 const QVariant &data, 
                 PublishType type = PublishType::AsyncReceiver);
    
    /**
     * @brief 获取某个主题的订阅者数量
     * @param topicId 主题ID
     * @return 订阅者数量
     */
    int getSubscriberCount(const QString &topicId) const;
    
    /**
     * @brief 获取所有主题ID列表
     * @return 主题ID列表
     */
    QStringList getAllTopics() const;
    
    /**
     * @brief 清空所有订阅
     */
    void clearAll();

protected:
    /**
     * @brief 自定义事件处理
     */
    bool event(QEvent *e) override;

private:
    explicit EventPubSub(QObject *parent = nullptr);
    ~EventPubSub();
    
    /**
     * @brief 同步执行回调
     */
    void executeCallbackSync(const SubscriberInfo &subscriber, const QVariant &data);
    
    /**
     * @brief 异步执行回调(当前线程)
     */
    void executeCallbackAsyncCurrent(const SubscriberInfo &subscriber, const QVariant &data);
    
    /**
     * @brief 异步执行回调(接收者线程)
     */
    void executeCallbackAsyncReceiver(const SubscriberInfo &subscriber, const QVariant &data);
    
    /**
     * @brief 获取或创建接收者的辅助对象(用于处理回调事件)
     */
    QObject* getOrCreateReceiverHelper(QObject *receiver);
    
    // 订阅者映射表:Map<主题ID, 订阅者列表>
    QMap<QString, QList<SubscriberInfo>> m_subscribers;
    
    // 接收者辅助对象映射:Map<接收者对象, 辅助对象>
    QMap<QObject*, ReceiverHelper*> m_receiverHelpers;
    
    // 线程安全锁
    mutable QMutex m_mutex;
    
    // 单例实例
    static EventPubSub* s_instance;
    static QMutex s_instanceMutex;
};


#endif // EVENTPUBSUB_H
eventpubsub.cpp
cpp 复制代码
#include "eventpubsub.h"
#include <QCoreApplication>
#include <QThread>
#include <QDebug>

// 静态成员初始化
EventPubSub* EventPubSub::s_instance = nullptr;
QMutex EventPubSub::s_instanceMutex;

// 自定义事件类型
QEvent::Type CallbackEvent::EventType = static_cast<QEvent::Type>(QEvent::User + 1);

EventPubSub::EventPubSub(QObject *parent)
    : QObject(parent)
{
}

EventPubSub::~EventPubSub()
{
    clearAll();
}

EventPubSub* EventPubSub::instance()
{
    if (!s_instance) {
        QMutexLocker locker(&s_instanceMutex);
        if (!s_instance) {
            s_instance = new EventPubSub();
        }
    }
    return s_instance;
}

bool EventPubSub::subscribe(const QString &topicId, 
                            QObject *receiver, 
                            std::function<void(const QVariant &)> callback)
{
    if (!receiver || !callback) {
        qWarning() << "EventPubSub::subscribe: Invalid receiver or callback";
        return false;
    }
    
    QMutexLocker locker(&m_mutex);
    
    SubscriberInfo info(receiver, callback);
    
    // 检查是否已经订阅
    if (m_subscribers.contains(topicId)) {
        QList<SubscriberInfo> &subscribers = m_subscribers[topicId];
        for (const SubscriberInfo &existing : subscribers) {
            if (existing == info) {
                qWarning() << "EventPubSub::subscribe: Already subscribed to topic:" << topicId;
                return false;
            }
        }
    }
    
    // 添加到订阅列表
    m_subscribers[topicId].append(info);
    
    qDebug() << "EventPubSub: Subscribed to topic" << topicId 
             << "with receiver" << receiver;
    
    return true;
}

bool EventPubSub::unsubscribe(const QString &topicId, QObject *receiver)
{
    if (!receiver) {
        qWarning() << "EventPubSub::unsubscribe: Invalid receiver";
        return false;
    }
    
    QMutexLocker locker(&m_mutex);
    
    if (!m_subscribers.contains(topicId)) {
        qWarning() << "EventPubSub::unsubscribe: Topic not found:" << topicId;
        return false;
    }
    
    QList<SubscriberInfo> &subscribers = m_subscribers[topicId];
    int removedCount = 0;
    
    // 移除所有匹配的订阅者
    for (int i = subscribers.size() - 1; i >= 0; --i) {
        if (subscribers[i].receiver == receiver) {
            subscribers.removeAt(i);
            removedCount++;
        }
    }
    
    // 如果列表为空,移除主题
    if (subscribers.isEmpty()) {
        m_subscribers.remove(topicId);
    }
    
    // 检查该receiver是否还有其他订阅
    bool hasOtherSubscriptions = false;
    if (removedCount > 0) {
        for (const QList<SubscriberInfo> &subList : m_subscribers.values()) {
            for (const SubscriberInfo &info : subList) {
                if (info.receiver == receiver) {
                    hasOtherSubscriptions = true;
                    break;
                }
            }
            if (hasOtherSubscriptions) {
                break;
            }
        }
        
        // 如果没有其他订阅,清理辅助对象
        if (!hasOtherSubscriptions && m_receiverHelpers.contains(receiver)) {
            ReceiverHelper *helper = m_receiverHelpers[receiver];
            m_receiverHelpers.remove(receiver);
            helper->deleteLater();
        }
        
        qDebug() << "EventPubSub: Unsubscribed from topic" << topicId 
                 << "for receiver" << receiver;
    }
    
    return removedCount > 0;
}

int EventPubSub::unsubscribeAll(QObject *receiver)
{
    if (!receiver) {
        return 0;
    }
    
    QMutexLocker locker(&m_mutex);
    
    int totalRemoved = 0;
    QStringList topicsToRemove;
    
    // 遍历所有主题
    for (auto it = m_subscribers.begin(); it != m_subscribers.end(); ++it) {
        QList<SubscriberInfo> &subscribers = it.value();
        int removedCount = 0;
        
        // 移除所有匹配的订阅者
        for (int i = subscribers.size() - 1; i >= 0; --i) {
            if (subscribers[i].receiver == receiver) {
                subscribers.removeAt(i);
                removedCount++;
            }
        }
        
        totalRemoved += removedCount;
        
        // 如果列表为空,标记为删除
        if (subscribers.isEmpty()) {
            topicsToRemove.append(it.key());
        }
    }
    
    // 移除空主题
    for (const QString &topic : topicsToRemove) {
        m_subscribers.remove(topic);
    }
    
    // 清理辅助对象(如果没有其他订阅了)
    if (totalRemoved > 0 && m_receiverHelpers.contains(receiver)) {
        ReceiverHelper *helper = m_receiverHelpers[receiver];
        m_receiverHelpers.remove(receiver);
        helper->deleteLater();
    }
    
    if (totalRemoved > 0) {
        qDebug() << "EventPubSub: Unsubscribed all topics for receiver" << receiver
                 << "Total:" << totalRemoved;
    }
    
    return totalRemoved;
}

void EventPubSub::publish(const QString &topicId, 
                         const QVariant &data, 
                         PublishType type)
{
    QMutexLocker locker(&m_mutex);
    
    if (!m_subscribers.contains(topicId)) {
        qDebug() << "EventPubSub::publish: No subscribers for topic:" << topicId;
        return;
    }
      
    // 复制订阅者列表(避免在回调中持有锁)
    QList<SubscriberInfo> subscribers = m_subscribers[topicId];
    locker.unlock();
    
    // 根据发布类型执行回调
    for (const SubscriberInfo &subscriber : subscribers) {
        // 检查接收者是否仍然有效
        if (!subscriber.receiver) {
            continue;
        }
        
        switch (type) {
        case PublishType::Sync:
            executeCallbackSync(subscriber, data);
            break;
            
        case PublishType::AsyncCurrent:
            executeCallbackAsyncCurrent(subscriber, data);
            break;
            
        case PublishType::AsyncReceiver:
            executeCallbackAsyncReceiver(subscriber, data);
            break;
        }
    }
}

void EventPubSub::executeCallbackSync(const SubscriberInfo &subscriber, const QVariant &data)
{
    // 同步执行:直接在当前线程调用
    if (subscriber.callback) {
        subscriber.callback(data);
    }
}

void EventPubSub::executeCallbackAsyncCurrent(const SubscriberInfo &subscriber, const QVariant &data)
{
    // 异步当前线程:通过事件循环执行
    if (!subscriber.callback) {
        return;
    }
    
    // 创建回调事件
    CallbackEvent *event = new CallbackEvent(subscriber.callback, data);
    
    // 投递到当前线程的事件队列
    QCoreApplication::postEvent(static_cast<QObject*>(this), event);
}

QObject* EventPubSub::getOrCreateReceiverHelper(QObject *receiver)
{
    if (!receiver) {
        return nullptr;
    }
    
    QMutexLocker locker(&m_mutex);
    
    // 检查是否已存在辅助对象
    if (m_receiverHelpers.contains(receiver)) {
        return m_receiverHelpers[receiver];
    }
    
   // 创建新的辅助对象(不设置父对象,避免跨线程问题)
    // 先创建对象,然后移动到接收者线程,这样就不会有跨线程父子关系的问题
    ReceiverHelper *helper = new ReceiverHelper(nullptr);
    helper->moveToThread(receiver->thread());
    m_receiverHelpers[receiver] = helper;
    
    return helper;
}

void EventPubSub::executeCallbackAsyncReceiver(const SubscriberInfo &subscriber, const QVariant &data)
{
    // 异步接收者:在接收者对象所在线程的事件循环中执行
    if (!subscriber.callback || !subscriber.receiver) {
        return;
    }
    
    // 检查接收者对象是否仍然有效
    if (!subscriber.receiver->thread()) {
        return;
    }
    
    // 获取或创建辅助对象
    QObject *helper = getOrCreateReceiverHelper(subscriber.receiver);
    if (!helper) {
        return;
    }
    
    // 创建回调事件
    CallbackEvent *event = new CallbackEvent(subscriber.callback, data);
    
    // 投递到辅助对象所在线程的事件队列(与接收者同一线程)
    QCoreApplication::postEvent(helper, event);
}

bool EventPubSub::event(QEvent *e)
{
    if (e->type() == CallbackEvent::EventType) {
        CallbackEvent *callbackEvent = static_cast<CallbackEvent *>(e);
        callbackEvent->execute();
        return true;
    }
    
    // 调用基类的event方法
    return QObject::event(e);
}

int EventPubSub::getSubscriberCount(const QString &topicId) const
{
    QMutexLocker locker(&m_mutex);
    
    if (m_subscribers.contains(topicId)) {
        return m_subscribers[topicId].size();
    }
    
    return 0;
}

QStringList EventPubSub::getAllTopics() const
{
    QMutexLocker locker(&m_mutex);
    return m_subscribers.keys();
}

void EventPubSub::clearAll()
{
    QMutexLocker locker(&m_mutex);
    
    // 清理所有辅助对象
    for (ReceiverHelper *helper : m_receiverHelpers.values()) {
        helper->deleteLater();
    }
    m_receiverHelpers.clear();
    
    // 清理所有订阅
    m_subscribers.clear();
    
    qDebug() << "EventPubSub: Cleared all subscriptions";
}

核心组件

  1. EventPubSub类:事件发布订阅系统的核心类
  2. SubscriberInfo结构体:存储订阅者信息(接收者对象、回调函数、线程信息)
  3. CallbackEvent类:自定义事件类,用于在事件循环中执行回调
  4. PublishType枚举:定义三种发布类型

数据结构

复制代码
Map<QString, QList<SubscriberInfo>>
  ├── Key: 主题ID(消息ID)
  └── Value: 订阅者列表
      └── SubscriberInfo
          ├── receiver: QObject指针
          ├── callback: 回调函数
          └── receiverThread: 接收者所在线程

线程安全机制

  • 使用QMutex保护订阅者映射表
  • 订阅和取消订阅操作都加锁保护
  • 发布消息时复制订阅者列表,避免长时间持有锁

技术详解

与Qt信号槽机制的关系

本组件基于Qt的事件驱动机制实现,与Qt信号槽系统有相似之处,但也有重要区别:

相似点

  • 都基于Qt的事件循环机制
  • 都支持异步执行和跨线程通信
  • 都使用事件队列实现延迟执行

区别

  • 信号槽:需要预定义信号和槽函数,编译时检查,类型安全
  • 发布订阅:动态订阅,运行时绑定,使用QVariant传递数据,更灵活

实现对比

cpp 复制代码
// Qt信号槽方式
class Sender : public QObject {
    Q_OBJECT
signals:
    void dataReady(int value);  // 需要预定义信号
};

class Receiver : public QObject {
    Q_OBJECT
public slots:
    void onDataReady(int value);  // 需要预定义槽函数
};

connect(sender, &Sender::dataReady, receiver, &Receiver::onDataReady);

// 发布订阅方式(本组件)
EventPubSub* pubSub = EventPubSub::instance();
pubSub->subscribe("topic", receiver, [](const QVariant &data) {
    // 动态回调,无需预定义
    int value = data.toInt();
});

关键技术点

1. ReceiverHelper辅助对象的设计

为什么需要辅助对象?

AsyncReceiver模式下,我们需要将回调事件投递到接收者对象所在线程的事件队列中执行。但是,直接向接收者对象投递自定义事件有一个问题:接收者对象可能没有重写event()方法来处理我们的CallbackEvent

解决方案

我们为每个接收者创建一个ReceiverHelper辅助对象,这个对象专门用来处理回调事件:

cpp 复制代码
class ReceiverHelper : public QObject {
    Q_OBJECT
protected:
    bool event(QEvent *e) override {
        if (e->type() == CallbackEvent::EventType) {
            CallbackEvent *callbackEvent = static_cast<CallbackEvent *>(e);
            callbackEvent->execute();  // 执行回调
            return true;
        }
        return QObject::event(e);
    }
};

工作流程

复制代码
发布消息(AsyncReceiver模式)
    ↓
获取或创建ReceiverHelper(与receiver同一线程)
    ↓
创建CallbackEvent事件
    ↓
投递到ReceiverHelper的事件队列
    ↓
ReceiverHelper的event()方法处理事件
    ↓
执行回调函数
2. 为什么不能用receiver作为父对象?

这是本组件设计中的一个关键技术点,涉及到Qt对象父子关系和线程安全。

问题分析

cpp 复制代码
// ❌ 错误的方式
ReceiverHelper *helper = new ReceiverHelper(receiver);  // receiver作为父对象
helper->moveToThread(receiver->thread());

为什么不能这样做?

  1. Qt的父子关系规则

    • Qt要求父子对象必须在同一个线程
    • 如果父对象和子对象在不同线程,会导致未定义行为
    • 当对象被移动到另一个线程时,其所有子对象也会被移动
  2. 线程移动的时机问题

    cpp 复制代码
    // 假设receiver在主线程,EventPubSub在另一个线程
    ReceiverHelper *helper = new ReceiverHelper(receiver);  
    // 此时helper也在EventPubSub的线程中(因为父对象receiver可能还没移动到目标线程)
    helper->moveToThread(receiver->thread());  
    // 移动helper时,Qt会检查父子关系,可能导致问题
  3. 跨线程父子关系的风险

    • 如果receiver和EventPubSub在不同线程,创建父子关系会违反Qt的线程规则
    • 可能导致对象在错误的线程中被删除
    • 可能导致事件处理在错误的线程中执行

正确的实现方式

cpp 复制代码
// ✅ 正确的方式
ReceiverHelper *helper = new ReceiverHelper(nullptr);  // 不设置父对象
helper->moveToThread(receiver->thread());  // 先移动到目标线程
m_receiverHelpers[receiver] = helper;  // 手动管理生命周期

关键要点

  1. 先创建对象,再移动线程

    • 创建时不设置父对象(使用nullptr
    • 然后调用moveToThread()移动到接收者线程
    • 这样避免了跨线程父子关系的问题
  2. 手动管理生命周期

    • 不使用Qt的父子关系自动删除
    • unsubscribe()clearAll()时手动删除辅助对象
    • 使用deleteLater()确保在正确的线程中删除
  3. 线程安全考虑

    • 辅助对象的创建和删除都在锁保护下进行
    • 确保多线程环境下的安全性

代码实现

cpp 复制代码
QObject* EventPubSub::getOrCreateReceiverHelper(QObject *receiver)
{
    if (!receiver) {
        return nullptr;
    }
    
    QMutexLocker locker(&m_mutex);
    
    // 检查是否已存在
    if (m_receiverHelpers.contains(receiver)) {
        return m_receiverHelpers[receiver];
    }
    
    // 创建新的辅助对象(不设置父对象,避免跨线程问题)
    // 先创建对象,然后移动到接收者线程,这样就不会有跨线程父子关系的问题
    ReceiverHelper *helper = new ReceiverHelper(nullptr);
    helper->moveToThread(receiver->thread());
    m_receiverHelpers[receiver] = helper;
    
    return helper;
}
3. 基于MetaCall事件的实现原理

本组件使用Qt的QEvent机制实现异步回调,这与Qt信号槽的QMetaCallEvent有相似之处。

事件类型定义

cpp 复制代码
// 自定义事件类型
QEvent::Type CallbackEvent::EventType = static_cast<QEvent::Type>(QEvent::User + 1);

事件类设计

cpp 复制代码
class CallbackEvent : public QEvent {
public:
    CallbackEvent(std::function<void(const QVariant &)> cb, const QVariant &data)
        : QEvent(EventType), m_callback(cb), m_data(data) {}
    
    void execute() {
        if (m_callback) {
            m_callback(m_data);  // 执行回调
        }
    }
    
private:
    std::function<void(const QVariant &)> m_callback;
    QVariant m_data;
};

事件处理流程

复制代码
1. 发布消息时创建CallbackEvent
    ↓
2. 通过QCoreApplication::postEvent()投递到事件队列
    ↓
3. 事件循环从队列取出事件
    ↓
4. 调用目标对象的event()方法
    ↓
5. ReceiverHelper的event()方法识别CallbackEvent
    ↓
6. 调用CallbackEvent::execute()
    ↓
7. 执行回调函数

与Qt信号槽的MetaCallEvent对比

特性 Qt信号槽 (QMetaCallEvent) 本组件 (CallbackEvent)
事件类型 Qt内部定义 自定义(QEvent::User + 1)
调用方式 通过元对象系统 通过std::function
类型检查 编译时检查 运行时检查(QVariant)
灵活性 需要预定义 动态回调
4. 线程安全的订阅管理

锁的使用策略

  1. 订阅操作:全程持有锁

    cpp 复制代码
    bool EventPubSub::subscribe(...) {
        QMutexLocker locker(&m_mutex);  // 自动加锁
        // 检查、添加订阅者
        // 锁在函数返回时自动释放
    }
  2. 发布操作:复制列表后释放锁

    cpp 复制代码
    void EventPubSub::publish(...) {
        QMutexLocker locker(&m_mutex);
        QList<SubscriberInfo> subscribers = m_subscribers[topicId];  // 复制
        locker.unlock();  // 立即释放锁
        
        // 在锁外执行回调,避免长时间持有锁
        for (const SubscriberInfo &subscriber : subscribers) {
            executeCallback(...);
        }
    }

为什么发布时要复制列表?

  • 避免在回调执行期间长时间持有锁
  • 防止死锁(回调函数可能再次调用subscribe/unsubscribe)
  • 提高并发性能
5. 内存管理策略

辅助对象的生命周期

  1. 创建时机:首次订阅时创建
  2. 删除时机
    • 取消订阅时检查是否还有其他订阅
    • 如果没有,删除辅助对象
    • 使用deleteLater()确保在正确的线程中删除
cpp 复制代码
// 取消订阅时的清理逻辑
if (!hasOtherSubscriptions && m_receiverHelpers.contains(receiver)) {
    ReceiverHelper *helper = m_receiverHelpers[receiver];
    m_receiverHelpers.remove(receiver);
    helper->deleteLater();  // 在正确的线程中删除
}

为什么使用deleteLater()?

  • Qt要求对象必须在创建它的线程中删除
  • deleteLater()将删除操作放入事件队列
  • 确保在正确的线程中执行删除

与Qt信号槽的深度对比

维度 Qt信号槽 本组件(发布订阅)
定义方式 编译时定义(signals/slots) 运行时动态订阅
类型安全 强类型(编译时检查) 弱类型(QVariant,运行时检查)
灵活性 需要预定义,不够灵活 动态绑定,非常灵活
性能 较高(直接调用或事件队列) 中等(事件队列+回调)
使用场景 固定的对象间通信 动态的、多对多通信
跨线程 自动处理(QueuedConnection) 自动处理(AsyncReceiver)
一对多 支持 支持
解耦程度 较高 极高(通过主题ID解耦)

适用场景选择

  • 使用Qt信号槽

    • 对象间有明确的、固定的通信关系
    • 需要编译时类型检查
    • 性能要求高
  • 使用发布订阅

    • 需要动态的、多对多通信
    • 发布者和订阅者不需要知道对方
    • 需要运行时灵活性

发布类型说明

1. Sync(同步)

在当前线程立即执行回调函数,阻塞直到回调完成。

cpp 复制代码
pubSub->publish("topic1", data, PublishType::Sync);
// 回调函数立即执行,完成后继续

适用场景

  • 需要立即得到结果的场景
  • 简单的数据处理
  • 性能要求高的场景

2. AsyncCurrent(异步当前线程)

将回调放入当前线程的事件队列,在事件循环的下一次迭代中执行。

cpp 复制代码
pubSub->publish("topic1", data, PublishType::AsyncCurrent);
// 立即返回,回调在事件循环中执行

适用场景

  • 需要延迟执行但仍在当前线程
  • 避免在同一个调用栈中递归执行
  • 批量操作后统一处理

3. AsyncReceiver(异步接收者)

将回调放入接收者对象所在线程的事件队列中执行。

cpp 复制代码
pubSub->publish("topic1", data, PublishType::AsyncReceiver);
// 立即返回,回调在接收者线程的事件循环中执行

适用场景

  • 跨线程通信
  • GUI更新(必须在主线程)
  • 线程安全的异步操作

使用方法

基本使用

cpp 复制代码
#include "eventpubsub.h"

// 获取单例实例
EventPubSub* pubSub = EventPubSub::instance();

// 创建接收者对象
QObject* receiver = new QObject();

// 订阅事件
pubSub->subscribe("topic1", receiver, [](const QVariant &data) {
    qDebug() << "Received data:" << data;
});

// 发布消息
QVariant message = QString("Hello World");
pubSub->publish("topic1", message, PublishType::AsyncReceiver);

// 取消订阅
pubSub->unsubscribe("topic1", receiver);

订阅示例

cpp 复制代码
// 方式1:使用lambda表达式
pubSub->subscribe("sensor/data", receiver, [](const QVariant &data) {
    int value = data.toInt();
    qDebug() << "Sensor value:" << value;
});

// 方式2:使用成员函数(需要包装)
class MyClass : public QObject {
    Q_OBJECT
public:
    void handleData(const QVariant &data) {
        qDebug() << "Data received:" << data;
    }
};

MyClass* obj = new MyClass();
pubSub->subscribe("topic1", obj, [obj](const QVariant &data) {
    obj->handleData(data);
});

// 方式3:使用函数对象
struct DataHandler {
    void operator()(const QVariant &data) {
        qDebug() << "Handled:" << data;
    }
};

pubSub->subscribe("topic1", receiver, DataHandler());

发布示例

cpp 复制代码
// 发布字符串
pubSub->publish("message", QVariant("Hello"), PublishType::AsyncReceiver);

// 发布整数
pubSub->publish("counter", QVariant(42), PublishType::Sync);

// 发布自定义结构(需要注册元类型)
struct MyData {
    int id;
    QString name;
};
Q_DECLARE_METATYPE(MyData)

MyData data{1, "Test"};
pubSub->publish("custom", QVariant::fromValue(data), PublishType::AsyncReceiver);

跨线程使用

cpp 复制代码
// 在工作线程中
class WorkerThread : public QThread {
protected:
    void run() override {
        EventPubSub* pubSub = EventPubSub::instance();
        msleep(1000);
        qWarning()<< "publish threadId:"<<QThread::currentThreadId();
        // 发布消息(自动在接收者线程执行)
        pubSub->publish("work/done", QVariant("Result"), 
                       PublishType::AsyncReceiver);
    }
};

// 在主线程中
class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow() {
        EventPubSub* pubSub = EventPubSub::instance();
        
        // 订阅(回调在主线程执行)
        pubSub->subscribe("work/done", this, [this](const QVariant &data) {
            // 安全地更新GUI(在主线程中)
            statusLabel->setText(data.toString());
            qWarning()<< "recv threadId:"<<QThread::currentThreadId();
            qWarning()<<"msg"<<data.toString();
        });
        
        // 启动工作线程
        WorkerThread* worker = new WorkerThread();
        worker->start();
    }
};

API参考

EventPubSub类

静态方法
cpp 复制代码
static EventPubSub* instance()

获取单例实例。

订阅方法
cpp 复制代码
bool subscribe(const QString &topicId, 
               QObject *receiver, 
               std::function<void(const QVariant &)> callback)

订阅事件。

参数

  • topicId:主题ID(消息ID)
  • receiver:订阅者对象指针
  • callback:回调函数

返回:订阅是否成功

取消订阅方法
cpp 复制代码
bool unsubscribe(const QString &topicId, QObject *receiver)

取消订阅指定主题。

cpp 复制代码
int unsubscribeAll(QObject *receiver)

取消某个对象的所有订阅。

发布方法
cpp 复制代码
void publish(const QString &topicId, 
             const QVariant &data, 
             PublishType type = PublishType::AsyncReceiver)

发布消息。

参数

  • topicId:主题ID
  • data:消息数据
  • type:发布类型(默认异步接收者)
查询方法
cpp 复制代码
int getSubscriberCount(const QString &topicId) const

获取某个主题的订阅者数量。

cpp 复制代码
QStringList getAllTopics() const

获取所有主题ID列表。

cpp 复制代码
void clearAll()

清空所有订阅。

构建说明

使用CMake

bash 复制代码
mkdir build
cd build
cmake ..
make

集成到项目

将以下文件添加到你的项目:

  • eventpubsub.h
  • eventpubsub.cpp

在CMakeLists.txt中添加:

cmake 复制代码
add_subdirectory(QtEventPubSub)
target_link_libraries(your_target QtEventPubSub)

或在.pro文件中:

pro 复制代码
SOURCES += QtEventPubSub/eventpubsub.cpp
HEADERS += QtEventPubSub/eventpubsub.h

注意事项

  1. 对象生命周期:确保订阅者对象在回调执行时仍然存在,否则可能导致崩溃
  2. 线程安全:虽然系统本身是线程安全的,但回调函数中的操作需要自行保证线程安全
  3. 性能考虑:同步模式性能最好,但会阻塞;异步模式不阻塞,但有一定开销
  4. 内存管理:组件内部使用辅助对象(ReceiverHelper)管理回调,这些对象不设置父对象,由组件自动管理生命周期
  5. 事件循环:异步模式需要目标线程有事件循环在运行
  6. 辅助对象设计:每个接收者会创建一个ReceiverHelper辅助对象,该对象不设置父对象(使用nullptr),以避免跨线程父子关系问题。这是Qt对象线程安全的重要设计决策

与ROS消息系统的对比

特性 ROS QtEventPubSub
消息类型 强类型(.msg文件) 弱类型(QVariant)
线程安全
发布模式 异步 同步/异步可选
跨进程 支持 不支持(单进程)
依赖 ROS核心 Qt Core
性能 中等 高(单进程)

最佳实践

  1. 使用有意义的主题ID :使用命名空间风格,如"sensor/temperature""control/motor"
  2. 合理选择发布类型 :根据场景选择合适的发布模式
    • 同线程快速调用:使用Sync
    • 需要延迟但仍在当前线程:使用AsyncCurrent
    • 跨线程通信:使用AsyncReceiver(默认)
  3. 及时取消订阅 :对象销毁前取消订阅,避免内存泄漏
    • 在对象的析构函数中调用unsubscribeAll()
    • 或使用QObject::destroyed信号自动清理
  4. 错误处理:在回调函数中添加错误处理逻辑
  5. 性能优化:对于高频消息,考虑批量处理或使用同步模式
  6. 与Qt信号槽的选择
    • 固定的一对一通信:优先使用Qt信号槽(类型安全、性能好)
    • 动态的多对多通信:使用发布订阅(灵活、解耦)
    • 需要运行时绑定:使用发布订阅
    • 需要编译时类型检查:使用Qt信号槽
  7. 线程安全注意事项
    • 组件本身是线程安全的
    • 但回调函数中的操作需要自行保证线程安全
    • 特别是访问共享数据时,需要额外的同步机制

示例场景

场景1:传感器数据订阅

cpp 复制代码
// 传感器发布数据
EventPubSub* pubSub = EventPubSub::instance();
pubSub->publish("sensor/temperature", QVariant(25.5), 
                PublishType::AsyncReceiver);

// 多个订阅者订阅
pubSub->subscribe("sensor/temperature", displayWidget, 
                  [](const QVariant &data) {
    // 更新显示
});

pubSub->subscribe("sensor/temperature", logger, 
                  [](const QVariant &data) {
    // 记录日志
});

场景2:命令发布

cpp 复制代码
// 发布命令
pubSub->publish("command/move", QVariant(QPoint(100, 200)), 
                PublishType::Sync);

// 订阅命令
pubSub->subscribe("command/move", robotController, 
                  [](const QVariant &data) {
    QPoint pos = data.toPoint();
    // 执行移动命令
});

场景3:状态通知

cpp 复制代码
// 状态变化通知
pubSub->publish("status/connected", QVariant(true), 
                PublishType::AsyncReceiver);

// 订阅状态变化
pubSub->subscribe("status/connected", statusBar, 
                  [](const QVariant &data) {
    bool connected = data.toBool();
    // 更新状态栏
});

许可证

本项目采用MIT许可证。

贡献

欢迎提交Issue和Pull Request。

更新日志

v1.0.0

  • 初始版本
  • 实现基本的发布订阅功能
  • 支持三种发布模式
  • 线程安全实现
相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript