2012-03-14 3 views
19

У меня есть приложение, которое должно реагировать на определенные события следующим образом:Каков правильный способ обработки событий на C++?

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

Это будет запущен на какую-нить. В некоторых других потоках операции будут создавать и запускать события. Как получить эти события для достижения вышеуказанного метода/класса? Какова правильная стратегия или архитектура для реализации обработки событий на C++?

+3

C++ язык действительно не имеет встроенной поддержки для такого рода вещи. Вам нужно будет использовать API для любой ОС, над которой вы работаете. –

+3

В общем, современный C++ использует сигналы и слоты (см. [Boost.Signals2] (http://www.boost.org/libs/signals2/)), а не передачу сообщений для событий. Подход, который вы показываете, уже архаичен, поэтому C++ не имеет ничего особенного в качестве языка для его поддержки. – ildjarn

+0

Сделайте несколько поисков BlockingQueue. Обработчик будет блокировать очередь get(), пока событие не будет отправлено в очередь. – Java42

ответ

9

Стандарт C++ не рассматривает события вообще. Обычно, однако, если вам нужны события, которые вы работаете в рамках, которая их предоставляет (SDL, Windows, Qt, GNOME и т. Д.), А также способы их ожидания, отправки и использования.

Помимо этого, вы можете посмотреть Boost.Signals2.

+5

Обратите внимание, что хотя Boost.Signals2 является потокобезопасным, он не обеспечивает механизм для событий очередей, отправляемых другим потоком. –

5

C++ не имеет встроенной поддержки событий. Вам нужно будет реализовать некоторую поточную очередь задач. Ваш основной поток обработки сообщений будет постоянно получать элементы из этой очереди и обрабатывать их.

Хорошим примером этого является стандартным 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++, но иллюстрирует подход.

6

C++ 11 и Boost имеют condition variables. Они являются средством для потока, чтобы разблокировать другой, ожидающий появления какого-либо события. Приведенная выше ссылка приводит вас к документации для boost::condition_variable и содержит пример кода, который показывает, как его использовать.

Если вам нужно отслеживать события (скажем, нажатия клавиш) и обрабатывать их в режиме FIFO (сначала в первом порядке), тогда вам придется использовать или сделать несколько многопоточных система очередей событий, как это предложено в некоторых других ответах.

9

Часто, очередей событий реализованы как 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_; 
}; 
+0

Я не могу использовать boost. Каковы мои возможности для реализации обработки событий? – Tariq

+0

@Tariq Затем используйте 'std ::' эквиваленты. –

+0

Извините, я не мог редактировать напрямую, но, думаю, вам не хватает пары '{}' скобок, чтобы заключить функцию 'void consumer_thread_function (EventQueue :: Ptr event_queue)' (верхняя часть раздела). – gbmhunter