在 C++ 中进行事件处理的正确方法是啥?

Posted

技术标签:

【中文标题】在 C++ 中进行事件处理的正确方法是啥?【英文标题】:What is the proper way of doing event handling in C++?在 C++ 中进行事件处理的正确方法是什么? 【发布时间】:2012-03-31 11:16:59 【问题描述】:

我有一个应用程序需要以下列方式响应某些事件:

void someMethodWithinSomeClass() 
    while (true) 
        wait for event;
        if (event == SomeEvent) 
            doSomething();
            continue;
        
        if (event == SomeOtherEvent) 
            doSomethingElse();
            continue;
        
     

这将是一些线程。在其他一些线程中,操作将创建并触发事件。

如何让这些事件到达上述方法/类?在 C++ 中实现事件处理的正确策略或架构是什么?

【问题讨论】:

C++ 语言实际上并没有对这类事情提供原生支持。您需要将 API 用于您正在开发的任何操作系统。 一般现代 C++ 使用信号和槽(参见Boost.Signals2)而不是事件的消息传递。您展示的方法已经过时,因此 C++ 没有什么特别的语言可以提供支持它。 对 BlockingQueue 进行一些搜索。处理程序将阻塞队列 get(),直到事件发布到队列。 事件需要排队吗?例如,假设这些事件是击键。在处理击键“A”的事件时,如果出现击键“B”和“C”,您的程序是否需要跟踪并“记住”击键“B”和“C”? uh ildjarn... 没有称为“信号和插槽”的 C++ 标准。 Boost 和 QT 具有使用该名称的 API。另一方面,有一种软件设计模式称为“观察者模式”,这就是信号和插槽。 【参考方案1】:

通常,事件队列被实现为command design pattern:

在面向对象编程中,命令模式是一种设计 对象用于表示和封装所有的模式 稍后调用方法所需的信息。这 信息包括方法名称、拥有该方法的对象 和方法参数的值。

在 C++ 中,拥有方法和方法参数值的对象是一个空函子(即不带参数的函子)。它可以使用boost::bind() 或C++11 lambdas 创建并包装到boost::function 中。

这是一个极简示例,如何在多个生产者和多个消费者线程之间实现事件队列。用法:

void consumer_thread_function(EventQueue::Ptr event_queue)
try 
    for(;;) 
        EventQueue::Event event(event_queue->consume()); // get a new event 
        event(); // and invoke it
    

catch(EventQueue::Stopped&) 


void some_work(int n) 
    std::cout << "thread " << boost::this_thread::get_id() << " : " << n << '\n';
    boost::this_thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(500));


int main()

    some_work(1);

    // create an event queue that can be shared between multiple produces and multiple consumers
    EventQueue::Ptr queue(new EventQueue);

    // create two worker thread and pass them a pointer to queue
    boost::thread worker_thread_1(consumer_thread_function, queue);
    boost::thread worker_thread_2(consumer_thread_function, queue);

    // tell the worker threads to do something
    queue->produce(boost::bind(some_work, 2));
    queue->produce(boost::bind(some_work, 3));
    queue->produce(boost::bind(some_work, 4));

    // tell the queue to stop
    queue->stop(true);

    // wait till the workers thread stopped
    worker_thread_2.join();
    worker_thread_1.join();

    some_work(5);

输出:

./test
thread 0xa08030 : 1
thread 0xa08d40 : 2
thread 0xa08fc0 : 3
thread 0xa08d40 : 4
thread 0xa08030 : 5

实施:

#include <boost/function.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <boost/smart_ptr/detail/atomic_count.hpp>
#include <iostream>

class EventQueue

public:
    typedef boost::intrusive_ptr<EventQueue> Ptr;
    typedef boost::function<void()> Event; // nullary functor
    struct Stopped ;

    EventQueue()
        : state_(STATE_READY)
        , ref_count_(0)
    

    void produce(Event event) 
        boost::mutex::scoped_lock lock(mtx_);
        assert(STATE_READY == state_);
        q_.push_back(event);
        cnd_.notify_one();
    

    Event consume() 
        boost::mutex::scoped_lock lock(mtx_);
        while(STATE_READY == state_ && q_.empty())
            cnd_.wait(lock);
        if(!q_.empty()) 
            Event event(q_.front());
            q_.pop_front();
            return event;
        
        // The queue has been stopped. Notify the waiting thread blocked in
        // EventQueue::stop(true) (if any) that the queue is empty now.
        cnd_.notify_all();
        throw Stopped();
    

    void stop(bool wait_completion) 
        boost::mutex::scoped_lock lock(mtx_);
        state_ = STATE_STOPPED;
        cnd_.notify_all();
        if(wait_completion) 
            // Wait till all events have been consumed.
            while(!q_.empty())
                cnd_.wait(lock);
        
        else 
            // Cancel all pending events.
            q_.clear();
        
    

private:
    // Disable construction on the stack. Because the event queue can be shared between multiple
    // producers and multiple consumers it must not be destroyed before the last reference to it
    // is released. This is best done through using a thread-safe smart pointer with shared
    // ownership semantics. Hence EventQueue must be allocated on the heap and held through
    // smart pointer EventQueue::Ptr.
    ~EventQueue() 
        this->stop(false);
    

    friend void intrusive_ptr_add_ref(EventQueue* p) 
        ++p->ref_count_;
    

    friend void intrusive_ptr_release(EventQueue* p) 
        if(!--p->ref_count_)
            delete p;
    

    enum State 
        STATE_READY,
        STATE_STOPPED,
    ;

    typedef std::list<Event> Queue;
    boost::mutex mtx_;
    boost::condition_variable cnd_;
    Queue q_;
    State state_;
    boost::detail::atomic_count ref_count_;
;

【讨论】:

我不能使用 boost。我有哪些实施事件处理的选项? @Tariq 然后使用std:: 等价物。 对不起,我无法直接编辑,但我认为您缺少一对 括号来括住 void consumer_thread_function(EventQueue::Ptr event_queue) 函数(代码的最顶部部分)。 @gbmhunter 不能编辑很好,因为代码格式正确。见en.cppreference.com/w/cpp/language/function-try-block【参考方案2】:

C++ 标准根本不涉及事件。但是,通常,如果您需要事件,您需要在一个框架内工作,该框架提供它们(SDL、Windows、Qt、GNOME 等)以及等待、调度和使用它们的方式。

除此之外,您可能还想查看Boost.Signals2。

【讨论】:

请注意,虽然 Boost.Signals2 是线程安全的,但它不提供排队事件以由另一个线程分派的机制。 @Max Lybbert,现在还是这样吗?微软有this【参考方案3】:

C++11 和 Boost 有condition variables。它们是线程解锁另一个正在等待某个事件发生的线程的一种方式。上面的链接将您带到std::condition_variable 的文档,并有一个代码示例说明如何使用它。

如果您需要跟踪事件(例如击键)并需要以 FIFO(先进先出)方式处理它们,那么您将不得不使用或制作某种多线程事件排队系统,如其他一些答案中所建议的那样。如果您选择不使用现有实现,则条件变量可以用作构建块来编写您自己的生产者/消费者队列。

【讨论】:

en.cppreference.com/w/cpp/thread/condition_variable【参考方案4】:

C++ 没有对事件的内置支持。您将不得不实现某种线程安全的任务队列。您的主消息处理线程会不断地从该队列中取出项目并对其进行处理。

驱动 Windows 应用程序的标准 Win32 消息泵就是一个很好的例子:

 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
 
   MSG msg;
   while(GetMessage(&msg, NULL, 0, 0) > 0)
   
     TranslateMessage(&msg);
     DispatchMessage(&msg);
   
   return msg.wParam;
 

其他线程可以Post向窗口发送消息,然后由该线程处理。

这使用 C 而不是 C++,但它说明了方法。

【讨论】:

以上是关于在 C++ 中进行事件处理的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 React 中处理事件时使用钩子的正确方法是啥

TypeScript 中更改处理程序的事件是啥“类型”?

在这种情况下,在 AngularJS 中处理事件的最佳方法是啥?

处理鼠标拖动的正确方法是啥?

在反应器内存储事件处理程序的最佳方式是啥

在 Presto 中进行基于范围的交叉连接的最佳方法是啥?