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++?
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 .
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_;
};
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.
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.
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