Eseménykezelés Aszinkron kommunikáció
Feladat Készítsünk egy stoppert, amely másodpercenként jelzi a múló időt. Ez a folyamat egy adott jelzés hatására induljon el; ugyanezen jelzés ismétléseinek hatására váltakozva szüneteljen illetve folytatódjon tovább; egy adott másik jelzés hatására álljon le. 2
Használati eset diagram: elemzés inicializálja az alkalmazást <<invoke>> idő kijelző nulla másodpercet mutat elindítja a stoppert <<invoke>> idő kijelző értéke másodpercenként eggyel nő user megállítja a stoppert <<invoke>> idő kijelző nem változik kilép a programból 3
Együttműködési diagram: elemzés click quit :Stopper start() stop() tick :Timer display(seconds) :LcdNumber 4
Osztálydiagram: elemzés Stopper Timer - seconds : int + start() + stop() «enumeration» Signal tick click quit LcdNumber + display(int) 5
Szekvencia diagram: elemzés :Stopper :Timer :LcdNumber start() loop tick [ per seconds] loop click loop tick [ per seconds] display() click loop tick [ per seconds] quit stop() 6
Stopper állapot-átmenet diagramja / seconds := 0 / lcd.display(seconds) / timer.start() tick stopped click click tick / lcd.display(++seconds) operate quit / timer.stop() quit / timer.stop() 7
Osztálydiagram: tervezés Stopper - seconds : int - currentstate : State - processevent(signal) : void Timer - active : bool + start() + stop() «enumeration» State operate stopped «enumeration» Signal tick click quit LcdNumber + display(int) 8
Stopper állapot-átmenet kódja processevent(event:signal) : void switch (currentstate) case stopped: switch (event) case click: currentstate = operate; case tick: ; case quit: exit; case operate: switch (event) case click: currentstate = stopped; case tick: lcd.display(++seconds); case quit: exit; «enumeration» Signal tick click quit «enumeration» State operate stopped 9
Megvalósítás Többszálú alkalmazásra van szükség, mert több aktív objektum van jelen, amelyek a stopper állapotgépének aszinkron módon üzennek. Külön szálon kell futnia a stopper állapotgépének, valamint a timer órajelet küldő metódusának. Az egyes szálak aktivitásának jelzésére olyan logikai változókra lesz szükség, amelyet több szál egymást kizárva képes használni. A fogadó objektum a több irányból érkező aszinkron üzeneteket egy eseménysorba gyűjti. Az eseménysorba a fogadó objektum által biztosított send() metódus szinkron hívása által kerülnek be események a fogadó objektum állapotgépétől eltérő szálakon. Az eseménysorból a fogadó objektum állapotgépe veszi ki az eseményeket. Az eseménysor műveleteinek kölcsönösen kizárásos módon kell működnie, és üres sor esetén a kivétel műveletét blokkolni kell. 10
Együttműködési diagram: megvalósítás :ThreadSafeQueue send(click) send(quit) enqueue() dequeue() :Stopper start() stop() send(tick) :Timer display(seconds) Az aszinkron hívást a :Stopper állapotgépétől eltérő szálakon kezdeményezett szinkron hívás helyettesíti. :LcdNumber 11
Osztálydiagram: megvalósítás Stopper - seconds : int - currentstate : State - active : bool + send(e : Signal):void - processevent(signal) : void - statemachine() : void «enumeration» State operate stopped «enumeration» Signal tick click quit eventqueue.enqueue(e) - timer - display while (active) do eventqueue.dequeue(e); processevent(e); od Signal - eventqueue Timer - active : bool + start() + stop() - statemachine() : void + display(int) LcdNumber ThreadSafeQueue + enqueue(t) + dequeue(t) T 12
Stopper osztály class Stopper enum Signal tick, click, quit; public: Stopper(); ~Stopper(); void send(signal event) _eventqueue.enqueue(event); private: enum State operate, stopped; void statemachine(); void processevent(signal event); State _currentstate; int _seconds; bool _active; std::thread _processorthread; külön szál a Stopper::stateMachine()-nek #include <thread> ; Timer _timer; LcdNumber _lcd; ThreadSafeQueue<Signal> _eventqueue; stopper.h 13
Stopper osztály Stopper::Stopper() : _currentstate(stopped),_seconds(0), _processorthread(&stopper::statemachine, this) _lcd.display(_seconds); _eventqueue.startqueue(); _timer.start(); Külön szálon indul el a Stopper állapotgépe. void Stopper::stateMachine() _active = true; while(_active) //amíg nincs terminálás Signal event; _eventqueue.dequeue(event); if(_active) processevent(event); Stopper::~Stopper() _processorthread.join(); _timer.stop(); _eventqueue.stopqueue(); Bevárja amíg az állapotgép leáll. stopper.cpp 14
Stopper eseménykezelője void Stopper::processEvent(Signal event) switch (_currentstate) // mi az állapot case stopped: switch (event) // mi az esemény case click: _currentstate = operate; break; case tick : break; case quit : _active = false; break; break; case operate: switch (event) // mi az esemény case click: _currentstate = stopped; break; case tick : _lcd.display(++_seconds); break; case quit : _active = false; break; break; stopper.cpp 15
Timer osztály class Stopper; class Timer typedef std::chrono::milliseconds milliseconds; public: Timer(Stopper *t) : _target(t), _active() _active = false; void start() ; void stop() ; private: void statemachine(); ; Stopper *_target; std::atomic_bool _active; std::thread _processorthread; szálbiztos logikai változó, amit több szál is használ #include <atomic> külön szál a Timer::stateMachine()-nek #include <thread> timer.h 16
Timer osztály void Timer::start() _active = true; _processorthread = new std::thread(&timer::statemachine, this); void Timer::stop() _active = false; _processorthread->join(); void Timer::stateMachine() std::condition_variable _cond; while (_active) std::mutex mu; std::unique_lock<std::mutex> lock(mu); _cond.wait_for(lock, milliseconds(1000)); _target->send(tick); Külön szálon indul el a Timer állapotgépe. speciális változó a várakozás megvalósításához #include <condition_variable> szemafor #include <mutex> Egy másodpercig blokkolja a szálat. timer.cpp 17
ThreadSafeQueue osztálysablon template <typename T> class ThreadSafeQueue public: ThreadSafeQueue() _active = false; void enqueue(const T& e) ; void dequeue(t& e) ; A _cond objektummal blokkolt összes szálnak engedélyezi a folytatást. void startqueue() _active = true; void stopqueue() _active = false; _cond.notify_all(); bool empty() const return _queue.empty(); private: std::queue<t> _queue; ; std::atomic_bool _active; std::mutex _mu; std::condition_variable _cond; threadsafequeue.hpp 18
ThreadSafeQueue osztálysablon template <typename T> void ThreadSafeQueue::enqueue(const T& e) std::unique_lock<std::mutex> lock(_mu); _queue.push(e); _cond.notify_one(); void ThreadSafeQueue::dequeue(T& e) std::unique_lock<std::mutex> lock(_mu); while(empty() && _active) _cond.wait(lock); if(_active) e = _queue.front(); _queue.pop(); threadsafequeue.hpp 19
LcdNumber osztály class LcdNumber public: void display(int seconds) int hours = seconds / 3600; int minutes = (seconds % 3600) / 60; std::string time = convertvalue(hours) + ":" + convertvalue(minutes) + ":" + convertvalue(((seconds % 3600) % 60)); std::cout << time << std::endl; private: std::string convertvalue(int val) return val<10? "0"+std::to_string(val) : std::to_string(val); ; lcdnumber.h 20
main() függvény int main() Stopper stopper; std::cout << "Choise option:" << std::endl; char o; do std::cin >> o; if(o == 's') stopper.send(click); while(o!= 'q'); stopper.send(quit); return 0; main.cpp 21
Stopper Qt-val fejlesztve QWidget Egyszerre ablak és olyan objektum, amely vezérlőket (timer, textbox, button) tárol, és támogatja az ezek közötti aszinkron üzenetek kezelését. QLCDNumber QPushButton #include <QApplication> QApplication #include "stopper.h" Többek között gondoskodik arról, hogy az int main(int argc, char *argv[]) események a megfelelő vezérlőhöz eljutva QApplication app(argc,argv); szignálként jelenjenek meg. Stopper *stopper = new Stopper; stopper->show(); return app.exec(); main.cpp 22
Stopper osztály, mint QWidget #include <QWidget> class QTimer; class QLCDNumber; class QPushButton; enum State stopped, operate; class Stopper: public Qwidget Q_OBJECT public: Stopper(QWidget *parent=0); protected: void closeevent(qcloseevent * event) stop() ; private: QTimer *_timer; A kilépés (quit) eseményéhez QLCDNumber *_lcd; rendeli a stop() eseménykezelőt, QPushButton *_button; amelyik a timer-t leállítja. State _currentstate; ; int _seconds; private slots: void onesecondpass(); // tick void buttonpressed(); // click void stop(); // quit több eseménykezelő függvény a processevent() helyett stopper.h 23
Stopper osztály Qt eseménykezelése Stopper::Stopper(QWidget *parent) : Qwidget(parent) setwindowtitle(tr("stopper")); resize(150, 60); _timer = new QTimer; _lcd = new QLCDNumber; _button = new QPushButton("Start/Stop"); connect(_timer, SIGNAL(timeout()), this, SLOT(oneSecondPass())); connect(_button, SIGNAL(clicked()), this, SLOT(buttonPressed())); _currentstate = stopped; _seconds = 0; _lcd->display(_seconds); _timer->start(1000); vezérlők elrendezésének és egyéb tulajdonságainak megadása események és kezelésük egymáshoz rendelése: tick ~ timeout() click ~ clicked() stopper.cpp 24
Stopper osztály Qt eseménykezelői Stopper::oneSecondPass() ez a kód a proccesevent() egyik részlete switch (_currentstate) case operate: _lcd->display(++_seconds); break; case stopped: break; Stopper::buttonPressed() ez a kód a proccesevent() másik részlete switch (_currentstate) case operate: _currentstate = stopped; break; case stopped: _currentstate = operate; break; Stopper::stop() _timer->stop(); stopper.cpp 25
Stopper.net alatt fejlesztve Form Ablak és objektum egyben, amely vezérlőket (timer, textbox, button) tárol, és támogatja az ezek közötti aszinkron üzenetek kezelését. TextBox Button static class Program [STAThread] static void Main() Application.EnableVisualStyles(); Application Többek között gondoskodik arról, hogy az események a megfelelő vezérlőhöz eljutva szignálként jelenjenek meg. Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Stopper()); program.cs 26
Stopper osztály, mint.net Form public partial class Stopper : Form enum State stopped, operate ; State _currentstate; DateTime _seconds = new DateTime(0); private System.Windows.Forms.Timer _timer; private System.Windows.Forms.Button _button; private System.Windows.Forms.TextBox _lcd; public Stopper() this.components = new System.ComponentModel.Container(); this.button = new System.Windows.Forms.Button(); this.lcd = new System.Windows.Forms.TextBox(); this.timer = new System.Windows.Forms.Timer(this.components); this.text = "Stopper"; this.button.text = "Start/Stop"; this.lcd.text = "00:00"; this.timer.interval = 1000; this.controls.add(this.lcd); this.controls.add(this.button); vezérlők elrendezésének és egyéb tulajdonságainak megadása Stopper.cs 27
Stopper osztály.net eseménykezelése események és kezelésük egymáshoz rendelése this._timer.tick += new System.EventHandler(oneSecondPass); this._button.click += new System.EventHandler(buttonPressed); this.formclosed += new System.Windows.Forms. FormClosedEventHandler(stop); _currentstate = State.stopped; display(); _timer.start(); quit FormedClosed private void onesecondpass(object sender, EventArgs e) private void buttonpressed(object sender, EventArgs e) private void stop(object sender,formclosedeventargs e)_timer.stop(); private void display() a tervvel ellentétben ez nem az lcd kijelző, hanem a _lcd.text = string.format("0:1", stopper metódusa seconds.minute.tostring().padleft(2, '0'), seconds.second.tostring().padleft(2, '0')); Stopper.Designer.cs 28