Jaki jest właściwy sposób obsługi zdarzeń w C++?

Mam aplikację, która musi reagować na określone zdarzenia w następujący sposób:

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

To byłoby uruchamianie to jakiś wątek. W niektórych innych wątkach operacje tworzyłyby i uruchamiały zdarzenia.

Jak sprawić, by te zdarzenia dotarły do powyższej metody / klasy? Jaka jest właściwa strategia lub Architektura implementacji obsługi zdarzeń w C++?

Author: Sotirios Delimanolis, 2012-03-15

4 answers

Standard C++ w ogóle nie odnosi się do zdarzeń. Zazwyczaj jednak, jeśli potrzebujesz zdarzeń, pracujesz w ramach frameworka, który je dostarcza (SDL , Windows, Qt, GNOME, itp.) oraz sposoby oczekiwania, wysyłki i korzystania z nich.

Poza tym, możesz spojrzeć na Boost.Signals2 .

 9
Author: Max Lybbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-14 22:51:09

Często kolejki zdarzeń są zaimplementowane jako wzorzec konstrukcyjny polecenia :

W programowaniu obiektowym wzorzec polecenia jest wzorem wzorzec, w którym obiekt jest używany do reprezentowania i hermetyzacji wszystkich informacje potrzebne do wywołania metody w późniejszym czasie. To informacja zawiera nazwę metody, obiekt, który jest właścicielem metody oraz wartości parametrów metody.

W C++, obiekt, który jest właścicielem metody i wartości dla metody parameters jest funktorem zerowym (tzn. funktorem, który nie pobiera argumentów). Może być utworzony za pomocą boost::bind() lub C++11 lambda i owinięty w boost::function.

Oto minimalistyczny przykład, jak zaimplementować kolejkę zdarzeń między wieloma wątkami producenta i wielu konsumentów. Sposób użycia:

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

Wyjścia:

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

Realizacja:

#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_;
};
 11
Author: Maxim Egorushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-17 21:15:36

C++11 i Boost mają zmienne warunkowe . Są one środkiem dla wątku, aby odblokować inny, który czeka na jakieś wydarzenie. Powyższy link prowadzi do dokumentacji boost::condition_variable i zawiera próbkę kodu, która pokazuje, jak z niej korzystać.

Jeśli musisz śledzić zdarzenia (np. naciśnięcia klawiszy) i przetwarzać je w sposób FIFO( first-in first-out), będziesz musiał użyć lub stworzyć wielowątkowy system kolejkowania zdarzeń, zgodnie z sugestią niektórych z nich inne odpowiedzi.

 6
Author: Emile Cormier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-14 23:04:49

C++ nie ma wbudowanej obsługi zdarzeń. Trzeba by zaimplementować jakąś kolejkę zadań bezpiecznych dla wątków. Twój główny wątek przetwarzania wiadomości będzie nieustannie pobierał elementy z tej kolejki i przetwarzał je.

Dobrym tego przykładem jest standardowa Pompa komunikatów Win32, która napędza Aplikacje windows:

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

Inne wątki mogą Post przekazać wiadomość do okna, które następnie będzie obsługiwane przez ten wątek.

Używa C zamiast C++, ale ilustruje podejdźcie.

 5
Author: Andrew Shepherd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-14 22:54:42