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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9711414/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:13:00  来源:igfitidea点击:

What is the proper way of doing event handling in C++?

c++multithreadingeventsevent-handling

提问by Sotirios Delimanolis

I have an application that needs to respond to certain events in the following manner:

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

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

This would be running is some thread. In some other threads, operations would create and fire the Events.

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

How do I get these Events to reach the above method/class? What is the proper strategy or architecture for implementing event handling in C++?

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

采纳答案by Max Lybbert

The C++ Standard doesn't address events at all. Usually, however, if you need events you are working within a framework that provides them (SDL, Windows, Qt, GNOME, etc.) and ways to wait for, dispatch and use them.

C++ 标准根本不处理事件。然而,通常,如果您需要事件,您将在提供它们(SDL、Windows、Qt、GNOME 等)以及等待、分派和使用它们的方法的框架内工作。

Aside from that, you may want to look at Boost.Signals2.

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

回答by Maxim Egorushkin

Often, event queues are implemented as command design pattern:

通常,事件队列被实现为命令设计模式

In object-oriented programming, the command pattern is a design pattern in which an object is used to represent and encapsulate all the information needed to call a method at a later time. This information includes the method name, the object that owns the method and values for the method parameters.

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

In C++, the object that own the method and values for the method parametersis a nullary functor (i.e. a functor that takes no arguments). It can be created using boost::bind()or C++11 lambdasand wrapped into boost::function.

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

Here is a minimalist example how to implement an event queue between multiple producer and multiple consumer threads. Usage:

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

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);
}

Outputs:

输出:

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

Implementation:

执行:

#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_;
};

回答by Emile Cormier

C++11 and Boost have condition variables. They are a means for a thread to unblock another one that is waiting for some event to occur. The link above brings you to the documentation for boost::condition_variable, and has a code sample that shows how to use it.

C++11 和 Boost有条件变量。它们是一个线程解除阻塞另一个正在等待某个事件发生的线程的方法。上面的链接将您带到 的文档boost::condition_variable,并有一个代码示例展示如何使用它。

If you need to keep track of events (say, keystrokes) and need to process them in a FIFO (first-in first-out) manner, then you'll have to use or make some kind of multi-threaded event queuing system, as suggested in some of the other answers.

如果您需要跟踪事件(例如,击键)并需要以 FIFO(先进先出)的方式处理它们,那么您将不得不使用或制作某种多线程事件排队系统,正如其他一些答案中所建议的那样。

回答by Andrew Shepherd

C++ does not have built-in support for events. You would have to implement some kind of thread-safe task queue. Your main message-processing thread would continually get items off this queue and process them.

C++ 没有对事件的内置支持。您必须实现某种线程安全的任务队列。您的主消息处理线程会不断地从这个队列中取出项目并处理它们。

A good example of this is standard Win32 message pump that drives windows applications:

一个很好的例子是驱动 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;
 }

Other threads can Posta message to a window, which will then be handled by this thread.

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

This uses C rather than C++, but it illustrates the approach.

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