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++?
Event Handling is the mechanism that controls the event and decides what should happen if an event occurs. This mechanism have the code which is known as event handler that is executed when an event occurs. Java Uses the Delegation Event Model to handle the events.
Event handlers are used in graphical user interface (GUI) applications to handle events such as button clicks and menu selections, raised by controls in the user interface. A single event handler can be used to process events raised by multiple controls.
In native C++ event handling, you set up an event source and event receiver using the event_source and event_receiver attributes, respectively, specifying type = native . These attributes allow the classes they're applied on to fire events and handle events in a native, non-COM context.
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 parameters is a nullary functor (i.e. a functor that takes no arguments). It can be created using boost::bind()
or C++11 lambdas and wrapped into 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_; };
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With