Qt Event Loop, Networking and I/O API
Total Page:16
File Type:pdf, Size:1020Kb
Qt event loop, networking and I/O API Thiago Macieira, Qt Core Maintainer San Francisco, November 2013 Who am I? • Open Source developer for 15 years • C++ developer for 13 years • Software Architect at Intel’s Open Source Technology Center (OTC) • Maintainer of two modules in the Qt Project ‒ QtCore and QtDBus • MBA and double degree in Engineering • Previously, led the “Qt Open Governance” project 2 © 2013 Intel Agenda • The event loop • Event loops and threads • Networking and I/O 3 © 2013 Intel The Event Loop Classes relating to the event loop • QAbstractEventDispatcher • QEventLoop • QCoreApplication • QTimer & QBasicTimer • QSocketNotifier & QWinEventNotifer • QThread 5 © 2013 Intel What an event loop does • While !interrupted: • If there are new events, dispatch them • Wait for more events • Event loops are the inner core of modern applications ‒ Headless servers and daemons, GUI applications, etc. ‒ Everything but shell tools and file-processing tools 6 © 2013 Intel Qt event loop feature overview • Receives and translates GUI • Integrates with: events ‒ Generic Unix select(2) ‒ NSEvents (on Mac) ‒ Glib ‒ MSG (on Windows) ‒ Mac’s CFRunLoop ‒ BPS events (on Blackberry) ‒ Windows’s WaitForMultipleObjects ‒ X11 events (using XCB) • Services timers ‒ Wayland events ‒ With coarse timer support ‒ Many others • Polls sockets • Manages the event queue (priority queue) • Polls Windows events • Allows for event filtering • Thread-safe waking up 7 © 2013 Intel QAbstractEventDispatcher • Each thread has one instance • Only of interest if you’re porting to a new OS... virtual bool processEvents(QEventLoop::ProcessEventsFlags flags) = 0; virtual bool hasPendingEvents() = 0; virtual void registerSocketNotifier(QSocketNotifier *notifier) = 0; virtual void unregisterSocketNotifier(QSocketNotifier *notifier) = 0; virtual void registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object) = 0; virtual bool unregisterTimer(int timerId) = 0; #ifdef Q_OS_WIN virtual bool registerEventNotifier(QWinEventNotifier *notifier) = 0; virtual void unregisterEventNotifier(QWinEventNotifier *notifier) = 0; #endif virtual void wakeUp() = 0; virtual void interrupt() = 0; 8 © 2013 Intel Data sources: QTimer • Millisecond-based timing • One signal: timeout() • Convenience function for single-shot timers • Three levels of coarseness: ‒ Precise: accurate to the millisecond ‒ Coarse (default): up to 5% error introduced so timers can be coalesced ‒ VeryCoarse: rounded to the nearest second QTimer timer; timer.setInterval(2000); QObject::connect(&timer, &QTimer::timeout, []() { qDebug() << "Timed out"; qApp->exit(1); }); 9 © 2013 Intel Data sources: QSocketNotifier • Polling for read, write or exceptional activity on sockets • On Unix, can poll anything that has a file descriptor • One signal: activated(int) PipeReader::PipeReader(int fd, QObject *parent) : QObject(parent), notifier(nullptr), m_fd(fd) { notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, &PipeReader::readFromFd); } 10 © 2013 Intel On Unix, it really means... Atomic variable while (!interrupted.load()) { struct timeval tm = maximumWaitTime(); fd_set readFds = enabledReadFds; fd_set writeFds = enabledWriteFds; fd_set exceptFds = enabledExceptFds; emit aboutToBlock(); ::select(maxFd + 1, &readFds, &writeFds, &exceptFds, &tm); emit awake(); sendPostedEvents(); dispatchFds(&readFds, &writeFds, &exceptFds); dispatchExpiredTimers(); } 11 © 2013 Intel Data sources: QWinEventNotifier • Windows is different... • Similar to QSocketNotifier • One signal: activated(HANDLE) 12 © 2013 Intel Data sinks: objects (slots and event handlers) • Objects receive signals and event • Starting with Qt 5, signals can be connected to lambdas, non- member functions, functors and some types of member functions public slots: void PipeReader::closeChannel() void start(); { ::close(m_fd); private slots: emit channelClosed(); void closeChannel(); } void readFromFd(); 13 © 2013 Intel Q{Core,Gui,}Application • One instance of QCoreApplication per application ‒ QGuiApplication if you want to use QWindow ‒ QApplication if you want to use QWidget • Defines the “GUI thread” and connects to the UI server • Starts the main event loop 14 © 2013 Intel A typical main(): exec() int main(int argc, char **argv) { QGuiApplication app(argc, argv); AnalogClockWindow clock; clock.show(); app.exec(); } 15 © 2013 Intel Simple example: timed read from a pipe (Unix) class PipeReader : public QObject { Q_OBJECT public: PipeReader(int fd, QObject *parent = 0); signals: void dataReceived(const QByteArray &data); void channelClosed(); void timeout(); private slots: void closeChannel(); void readFromFd(); }; 16 © 2013 Intel Simple example: timed read from a pipe (Unix) int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); PipeReader reader(fileno(stdin)); QObject::connect(&reader, &PipeReader::channelClosed, &QCoreApplication::quit); QObject::connect(&reader, &PipeReader::dataReceived, [](const QByteArray &data) { qDebug() << "Received" << data.length() << "bytes"; }); QTimer timer; timer.setInterval(2000); QObject::connect(&timer, &QTimer::timeout, []() { qDebug() << "Timed out"; qApp->exit(1); }); timer.start(); return a.exec(); } 17 © 2013 Intel Nesting event loops: QEventLoop • Make non-blocking operations into “blocking” ones • Without freezing the UI • Avoid if you can! 18 © 2013 Intel Using the pipe reader with QEventLoop QByteArray nestedLoop() { QEventLoop loop; QByteArray data; PipeReader reader(fileno(stdin)); QObject::connect(&reader, &PipeReader::dataReceived, [&](const QByteArray &newData) { data += newData; }); QObject::connect(&reader, &PipeReader::channelClosed, &loop, &QEventLoop::quit); loop.exec(); return data; } 19 © 2013 Intel exec() in other classes • It appears in modal GUI classes ‒ QDialog ‒ QProgressDialog ‒ QFileDialog ‒ QMenu • Like QEventLoop’s exec(), avoid if you can! ‒ Use show() and return to the main loop 20 © 2013 Intel Using the pipe reader with QProgressDialog int progressDialog() { QProgressDialog dialog("Bytes received", "Stop reading", 0, 100); // expect 100 bytes PipeReader reader(fileno(stdin)); QObject::connect(&reader, &PipeReader::channelClosed, &dialog, &QProgressDialog::accept); QObject::connect(&reader, &PipeReader::dataReceived, [&](const QByteArray &data) { qDebug() << "Received" << data.length() << "bytes"; dialog.setValue(qMin(dialog.maximum(), dialog.value() + data.length())); }); dialog.exec(); return dialog.result() == QDialog::Accepted ? 0 : 1; } 22 © 2013 Intel Threading with Qt QThread • Manages a new thread in the application ‒ Starting, waiting, requesting or forcing exit, notifying of exit, etc. • Also provides some methods for the current thread ‒ Sleeping, yielding 24 © 2013 Intel Why threads? Good reasons Bad reasons • Calling blocking functions • To use sleep() • CPU-intensive work • Networking and I/O • Real-time work ‒ Except for scalability • Scalability 25 © 2013 Intel A typical thread (without event loops) class MyThread : public QThread { public: void run() { // code that takes a long time to run goes here } }; 26 © 2013 Intel Thread example: blocking read from a pipe void run() { forever { QByteArray buffer(4096, Qt::Uninitialized); ssize_t r = ::read(m_fd, buffer.data(), buffer.size()); if (r <= 0) return; buffer.resize(r); emit dataReceived(buffer); } } 27 © 2013 Intel Threads and event loops: thread affinity • Each QObject is associated with one thread • Where its event handlers and slots will be called • Where the object must be deleted 28 © 2013 Intel Moving objects to threads ThreadedPipeReader2::ThreadedPipeReader2(int fd, QObject *parent) : QThread(parent), reader(new PipeReader(fd)) { reader->moveToThread(this); connect(reader, &PipeReader::dataReceived, this, &ThreadedPipeReader2::dataReceived, Qt::DirectConnection); connect(reader, &PipeReader::channelClosed, [=]() { delete reader; quit(); }); } Automatically moves children PipeReader::PipeReader(int fd, QObject *parent) : QObject(parent), notifier(nullptr), m_fd(fd) { notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, &PipeReader::readFromFd); } 29 © 2013 Intel Connection types • DirectConnection: ‒ Slot is called immediately, by simple function call ‒ Synchronous, same thread • QueuedConnection: ‒ An event is posted so the slot is called later ‒ Asynchronous, potentially run in another thread • BlockingQueuedConnection: ‒ Same as QueuedConnection, but includes a semaphore to wait ‒ Synchronous, always run in another thread • AutoConnection: choose at emission time 30 © 2013 Intel A typical thread with event loop void ThreadedPipeReader2::run() { // preparation goes here exec(); // clean-up goes here } 31 © 2013 Intel Qt I/O and Networking The I/O classes Random access Sequential access • QFile • QNetworkReply ‒ QTemporaryFile • QProcess ‒ QSaveFile • QLocalSocket • QBuffer • QTcpSocket • QUdpSocket • QWebSocket (coming) 33 © 2013 Intel QIODevice • Base class of all Qt I/O classes • Provides: ‒ CRLF translation ‒ read(), readAll(), readLine(), write(), peek() ‒ Signals: readyRead(), bytesWritten() ‒ Buffering support, bytesAvailable(), bytesToWrite() ‒ atEnd(), size(), pos(), seek() 34 © 2013 Intel Random-access I/O characteristics • Defining feature: seek() and size() • Device can be put in unbuffered mode • All I/O is synchronous ‒ I/O latency is not considered as blocking • No notification support (signals not emitted) 35 © 2013 Intel Random-access