From 2b7d98ef8fbd6cf49326fa0bbf154e9bacbb7b49 Mon Sep 17 00:00:00 2001 From: Sven Anderson Date: Thu, 8 Sep 2011 17:40:55 +0200 Subject: [PATCH] Allow to create a custom event dispatcher for specific QThreads. QAbstractEventDispatcher() does no longer install itself into the current thread. Instead the new methods QThread::setEventDispatcher() and QCoreApplication::setEventDispatcher() allow to install a custom event dispatcher into any QThread as long as there is no default event dispatcher created yet. That is, before the thread has been started with QThread::start() or, in case of the main thread, before QCoreApplication has been instantiated. Change-Id: I7367e13d8d8aebed5a5651260bb69b8818eb1b90 Reviewed-by: Olivier Goffart Reviewed-by: Bradley T. Hughes --- src/corelib/kernel/qabstracteventdispatcher.cpp | 29 ++------- src/corelib/kernel/qabstracteventdispatcher_p.h | 1 - src/corelib/kernel/qcoreapplication.cpp | 29 ++++++++- src/corelib/kernel/qcoreapplication.h | 3 + src/corelib/thread/qthread.cpp | 32 ++++++++++ src/corelib/thread/qthread.h | 4 ++ src/corelib/thread/qthread_unix.cpp | 6 +- src/corelib/thread/qthread_win.cpp | 7 +- .../qcoreapplication/tst_qcoreapplication.cpp | 55 ++++++++++++++++ tests/auto/corelib/thread/qthread/tst_qthread.cpp | 74 ++++++++++++++++++++++ 10 files changed, 211 insertions(+), 29 deletions(-) diff --git a/src/corelib/kernel/qabstracteventdispatcher.cpp b/src/corelib/kernel/qabstracteventdispatcher.cpp index 10de2ca..b936ac4 100644 --- a/src/corelib/kernel/qabstracteventdispatcher.cpp +++ b/src/corelib/kernel/qabstracteventdispatcher.cpp @@ -49,16 +49,6 @@ QT_BEGIN_NAMESPACE -void QAbstractEventDispatcherPrivate::init() -{ - Q_Q(QAbstractEventDispatcher); - if (threadData->eventDispatcher != 0) { - qWarning("QAbstractEventDispatcher: An event dispatcher has already been created for this thread"); - } else { - threadData->eventDispatcher = q; - } -} - // we allow for 2^24 = 8^8 = 16777216 simultaneously running timers struct QtTimerIdFreeListConstants : public QFreeListDefaultConstants { @@ -127,8 +117,9 @@ void QAbstractEventDispatcherPrivate::releaseTimerId(int timerId) instance() and call functions on the QAbstractEventDispatcher object that is returned. If you want to use your own instance of QAbstractEventDispatcher or of a QAbstractEventDispatcher - subclass, you must create your instance \e before you create the - QApplication object. + subclass, you must install it with QCoreApplication::setEventDispatcher() + or QThread::setEventDispatcher() \e before a default event dispatcher has + been installed. The main event loop is started by calling QCoreApplication::exec(), and stopped by calling @@ -145,29 +136,21 @@ void QAbstractEventDispatcherPrivate::releaseTimerId(int timerId) reimplementation of QAbstractEventDispatcher that merges Qt and Motif events together. - \sa QEventLoop, QCoreApplication + \sa QEventLoop, QCoreApplication, QThread */ /*! Constructs a new event dispatcher with the given \a parent. */ QAbstractEventDispatcher::QAbstractEventDispatcher(QObject *parent) - : QObject(*new QAbstractEventDispatcherPrivate, parent) -{ - Q_D(QAbstractEventDispatcher); - d->init(); -} + : QObject(*new QAbstractEventDispatcherPrivate, parent) {} /*! \internal */ QAbstractEventDispatcher::QAbstractEventDispatcher(QAbstractEventDispatcherPrivate &dd, QObject *parent) - : QObject(dd, parent) -{ - Q_D(QAbstractEventDispatcher); - d->init(); -} + : QObject(dd, parent) {} /*! Destroys the event dispatcher. diff --git a/src/corelib/kernel/qabstracteventdispatcher_p.h b/src/corelib/kernel/qabstracteventdispatcher_p.h index 620a449..31c579d 100644 --- a/src/corelib/kernel/qabstracteventdispatcher_p.h +++ b/src/corelib/kernel/qabstracteventdispatcher_p.h @@ -67,7 +67,6 @@ public: inline QAbstractEventDispatcherPrivate() : event_filter(0) { } - void init(); QAbstractEventDispatcher::EventFilter event_filter; static int allocateTimerId(); diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index 9408d41..c30f613 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -660,8 +660,10 @@ void QCoreApplication::init() d->createEventDispatcher(); Q_ASSERT(QCoreApplicationPrivate::eventDispatcher != 0); - if (!QCoreApplicationPrivate::eventDispatcher->parent()) + if (!QCoreApplicationPrivate::eventDispatcher->parent()) { QCoreApplicationPrivate::eventDispatcher->moveToThread(d->threadData->thread); + QCoreApplicationPrivate::eventDispatcher->setParent(this); + } d->threadData->eventDispatcher = QCoreApplicationPrivate::eventDispatcher; @@ -2517,6 +2519,31 @@ bool QCoreApplication::hasPendingEvents() return false; } +/*! + Returns a pointer to the event dispatcher object for the main thread. If no + event dispatcher exists for the thread, this function returns 0. +*/ +QAbstractEventDispatcher *QCoreApplication::eventDispatcher() +{ + if (QCoreApplicationPrivate::theMainThread) + return QCoreApplicationPrivate::theMainThread->eventDispatcher(); + return 0; +} + +/*! + Sets the event dispatcher for the main thread to \a eventDispatcher. This + is only possible as long as there is no event dispatcher installed yet. That + is, before QCoreApplication has been instantiated. This method takes + ownership of the object. +*/ +void QCoreApplication::setEventDispatcher(QAbstractEventDispatcher *eventDispatcher) +{ + QThread *mainThread = QCoreApplicationPrivate::theMainThread; + if (!mainThread) + mainThread = QThread::currentThread(); // will also setup theMainThread + mainThread->setEventDispatcher(eventDispatcher); +} + /* \fn void QCoreApplication::watchUnixSignal(int signal, bool watch) \internal diff --git a/src/corelib/kernel/qcoreapplication.h b/src/corelib/kernel/qcoreapplication.h index ce3f349..4d3ee9b 100644 --- a/src/corelib/kernel/qcoreapplication.h +++ b/src/corelib/kernel/qcoreapplication.h @@ -61,6 +61,7 @@ class QTextCodec; class QTranslator; class QPostEventList; class QStringList; +class QAbstractEventDispatcher; #define qApp QCoreApplication::instance() @@ -114,6 +115,8 @@ public: static void removePostedEvents(QObject *receiver); static void removePostedEvents(QObject *receiver, int eventType); static bool hasPendingEvents(); + static QAbstractEventDispatcher *eventDispatcher(); + static void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher); virtual bool notify(QObject *, QEvent *); diff --git a/src/corelib/thread/qthread.cpp b/src/corelib/thread/qthread.cpp index 6cc8a48..17b0f2c 100644 --- a/src/corelib/thread/qthread.cpp +++ b/src/corelib/thread/qthread.cpp @@ -757,4 +757,36 @@ QThread::QThread(QThreadPrivate &dd, QObject *parent) #endif // QT_NO_THREAD +/*! + Returns a pointer to the event dispatcher object for the thread. If no event + dispatcher exists for the thread, this function returns 0. +*/ +QAbstractEventDispatcher *QThread::eventDispatcher() const +{ + Q_D(const QThread); + return d->data->eventDispatcher; +} + +/*! + Sets the event dispatcher for the thread to \a eventDispatcher. This is + only possible as long as there is no event dispatcher installed for the + thread yet. That is, before the thread has been started with start() or, in + case of the main thread, before QCoreApplication has been instantiated. + This method takes ownership of the object. +*/ +void QThread::setEventDispatcher(QAbstractEventDispatcher *eventDispatcher) +{ + Q_D(QThread); + if (d->data->eventDispatcher != 0) { + qWarning("QThread::setEventDispatcher: An event dispatcher has already been created for this thread"); + } else { + eventDispatcher->moveToThread(this); + if (eventDispatcher->thread() == this) // was the move successful? + d->data->eventDispatcher = eventDispatcher; + else + qWarning("QThread::setEventDispatcher: Could not move event dispatcher to target thread"); + } +} + + QT_END_NAMESPACE diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h index 89a3ad8..baad793 100644 --- a/src/corelib/thread/qthread.h +++ b/src/corelib/thread/qthread.h @@ -54,6 +54,7 @@ QT_MODULE(Core) class QThreadData; class QThreadPrivate; +class QAbstractEventDispatcher; #ifndef QT_NO_THREAD class Q_CORE_EXPORT QThread : public QObject @@ -92,6 +93,9 @@ public: void exit(int retcode = 0); + QAbstractEventDispatcher *eventDispatcher() const; + void setEventDispatcher(QAbstractEventDispatcher *eventDispatcher); + public Q_SLOTS: void start(Priority = InheritPriority); void terminate(); diff --git a/src/corelib/thread/qthread_unix.cpp b/src/corelib/thread/qthread_unix.cpp index 46bc641..8c6fefc 100644 --- a/src/corelib/thread/qthread_unix.cpp +++ b/src/corelib/thread/qthread_unix.cpp @@ -289,8 +289,10 @@ void *QThreadPrivate::start(void *arg) data->quitNow = thr->d_func()->exited; } - // ### TODO: allow the user to create a custom event dispatcher - createEventDispatcher(data); + if (data->eventDispatcher) // custom event dispatcher set? + data->eventDispatcher->startingUp(); + else + createEventDispatcher(data); emit thr->started(); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); diff --git a/src/corelib/thread/qthread_win.cpp b/src/corelib/thread/qthread_win.cpp index 80d7f3f..5b74bad 100644 --- a/src/corelib/thread/qthread_win.cpp +++ b/src/corelib/thread/qthread_win.cpp @@ -315,8 +315,11 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi QMutexLocker locker(&thr->d_func()->mutex); data->quitNow = thr->d_func()->exited; } - // ### TODO: allow the user to create a custom event dispatcher - createEventDispatcher(data); + + if (data->eventDispatcher) // custom event dispatcher set? + data->eventDispatcher->startingUp(); + else + createEventDispatcher(data); #if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) && !defined(Q_OS_WINCE) // sets the name of the current thread. diff --git a/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp b/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp index 62275f8..09f31414 100644 --- a/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp +++ b/tests/auto/corelib/kernel/qcoreapplication/tst_qcoreapplication.cpp @@ -62,6 +62,7 @@ private slots: void reexec(); void execAfterExit(); void eventLoopExecAfterExit(); + void customEventDispatcher(); }; class EventSpy : public QObject @@ -586,5 +587,59 @@ void tst_QCoreApplication::eventLoopExecAfterExit() QCOMPARE(loop.exec(), 0); } +class DummyEventDispatcher : public QAbstractEventDispatcher { +public: + DummyEventDispatcher() : QAbstractEventDispatcher(), visited(false) {} + bool processEvents(QEventLoop::ProcessEventsFlags) { + visited = true; + emit awake(); + QCoreApplication::sendPostedEvents(); + return false; + } + bool hasPendingEvents() { + extern uint qGlobalPostedEventsCount(); // from qapplication.cpp + return qGlobalPostedEventsCount(); + } + void registerSocketNotifier(QSocketNotifier *) {} + void unregisterSocketNotifier(QSocketNotifier *) {} + void registerTimer(int , int , QObject *) {} + bool unregisterTimer(int ) { return false; } + bool unregisterTimers(QObject *) { return false; } + QList registeredTimers(QObject *) const { return QList(); } + void wakeUp() {} + void interrupt() {} + void flush() {} + + bool visited; +}; + +void tst_QCoreApplication::customEventDispatcher() +{ + // there should be no ED yet + QVERIFY(!QCoreApplication::eventDispatcher()); + DummyEventDispatcher *ed = new DummyEventDispatcher; + QCoreApplication::setEventDispatcher(ed); + // the new ED should be set + QCOMPARE(QCoreApplication::eventDispatcher(), ed); + // test the alternative API of QAbstractEventDispatcher + QCOMPARE(QAbstractEventDispatcher::instance(), ed); + QWeakPointer weak_ed(ed); + QVERIFY(!weak_ed.isNull()); + { + int argc = 1; + char *arg0 = "tst_qcoreapplication"; + char *argv[] = { arg0 }; + QCoreApplication app(argc, argv); + // instantiating app should not overwrite the ED + QCOMPARE(QCoreApplication::eventDispatcher(), ed); + QMetaObject::invokeMethod(&app, "quit", Qt::QueuedConnection); + app.exec(); + // the custom ED has really been used? + QVERIFY(ed->visited); + } + // ED has been deleted? + QVERIFY(weak_ed.isNull()); +} + QTEST_APPLESS_MAIN(tst_QCoreApplication) #include "tst_qcoreapplication.moc" diff --git a/tests/auto/corelib/thread/qthread/tst_qthread.cpp b/tests/auto/corelib/thread/qthread/tst_qthread.cpp index 6d7a02b..fcf993b 100644 --- a/tests/auto/corelib/thread/qthread/tst_qthread.cpp +++ b/tests/auto/corelib/thread/qthread/tst_qthread.cpp @@ -107,6 +107,8 @@ private slots: void startAndQuitCustomEventLoop(); void isRunningInFinished(); + void customEventDispatcher(); + #ifndef Q_OS_WINCE void stressTest(); #endif @@ -1218,5 +1220,77 @@ void tst_QThread::isRunningInFinished() } } +class DummyEventDispatcher : public QAbstractEventDispatcher { +public: + DummyEventDispatcher() : QAbstractEventDispatcher(), visited(false) {} + bool processEvents(QEventLoop::ProcessEventsFlags) { + visited = true; + emit awake(); + QCoreApplication::sendPostedEvents(); + return false; + } + bool hasPendingEvents() { + extern uint qGlobalPostedEventsCount(); // from qapplication.cpp + return qGlobalPostedEventsCount(); + } + void registerSocketNotifier(QSocketNotifier *) {} + void unregisterSocketNotifier(QSocketNotifier *) {} + void registerTimer(int , int , QObject *) {} + bool unregisterTimer(int ) { return false; } + bool unregisterTimers(QObject *) { return false; } + QList registeredTimers(QObject *) const { return QList(); } + void wakeUp() {} + void interrupt() {} + void flush() {} + + bool visited; +}; + +class ThreadObj : public QObject +{ + Q_OBJECT +public slots: + void visit() { + emit visited(); + } +signals: + void visited(); +}; + +void tst_QThread::customEventDispatcher() +{ + QThread thr; + // there should be no ED yet + QVERIFY(!thr.eventDispatcher()); + DummyEventDispatcher *ed = new DummyEventDispatcher; + thr.setEventDispatcher(ed); + // the new ED should be set + QCOMPARE(thr.eventDispatcher(), ed); + // test the alternative API of QAbstractEventDispatcher + QCOMPARE(QAbstractEventDispatcher::instance(&thr), ed); + thr.start(); + // start() should not overwrite the ED + QCOMPARE(thr.eventDispatcher(), ed); + + ThreadObj obj; + obj.moveToThread(&thr); + // move was successful? + QCOMPARE(obj.thread(), &thr); + QEventLoop loop; + connect(&obj, SIGNAL(visited()), &loop, SLOT(quit()), Qt::QueuedConnection); + QMetaObject::invokeMethod(&obj, "visit", Qt::QueuedConnection); + loop.exec(); + // test that the ED has really been used + QVERIFY(ed->visited); + + QWeakPointer weak_ed(ed); + QVERIFY(!weak_ed.isNull()); + thr.quit(); + // wait for thread to be stopped + QVERIFY(thr.wait(30000)); + // test that ED has been deleted + QVERIFY(weak_ed.isNull()); +} + QTEST_MAIN(tst_QThread) #include "tst_qthread.moc" -- 2.7.4