注意,本文試圖經過源碼解釋下面的問題:app
子QObject必須在其parent關聯的線程內建立函數
調用moveToThread()的對象其parent必須爲0oop
事件驅動的對象要在單一線程內使用源碼分析
QTimer、network模塊的QTcpSocket等等post
爲何不能在非關聯線程內開啓QTimer或者鏈接QTcpSocket到服務器?this
刪除QThread對象前,確保線程內全部對象都沒銷燬spa
AutoConnection的是是非非,兩種說法孰是孰非?.net
其一:信號關聯的線程和槽關聯的線程不一致時,則Queued方式線程
其二:信號發射時的當前線程和槽函數關聯的線程不一致時,則Queued方式
但很顯然,我沒作到這一點(能力所限,現階段我只能讓本身勉強明白),儘管如此,本文應該仍是提供了不少你理解這些問題所需的背景知識。
線程關聯性(Thread Affinity)???
什麼東西? |
每個QObject都會和一個線程相關聯 |
QObject 是線程感知的,每個QObject及派生類的對象被建立時都會將其所在線程的引用保存下來(能夠經過QObject::thread()返回)。 |
幹嗎用的? |
用於事件系統 |
QObject對象的事件處理函數始終要在其所關聯線程的上下文中執行。 |
能否改變? |
使用QObject::moveToThread()能夠將QObject對象從一個線程移動到另外一個線程。 |
看看QObject的初始化(看兩點):
保存當前線程(QThreadData)的指針。
若是其parent關聯的線程和當前線程不一致,parent會強制置0。
這要求子對象必須在其parent關聯的線程內建立。
當使用QThread時,你不能將QThread對象做爲在新線程中所建立的對象的parent。
QObject::QObject(QObject *parent) : d_ptr(new QObjectPrivate) { Q_D(QObject); d->threadData = (parent && !parent->thread()) ? parent->d_func()->threadData : QThreadData::current(); if (parent) { if (!check_parent_thread(parent, parent ? parent->d_func()->threadData : 0, d->threadData)) parent = 0; setParent(parent); }
看看moveToThread()的代碼(咱們此處只關心限制條件):
parent非0的對象不能被移動!
QWidget及其派生類對象不能被移動!
該函數必須在對象關聯的線程內調用!
void QObject::moveToThread(QThread *targetThread) { Q_D(QObject); if (d->parent != 0) { qWarning("QObject::moveToThread: Cannot move objects with a parent"); return; } if (d->isWidget) { qWarning("QObject::moveToThread: Widgets cannot be moved to a new thread"); return; } QThreadData *currentData = QThreadData::current(); QThreadData *targetData = targetThread ? QThreadData::get2(targetThread) : new QThreadData(0); if (d->threadData->thread == 0 && currentData == targetData) { // one exception to the rule: we allow moving objects with no thread affinity to the current thread currentData = d->threadData; } else if (d->threadData != currentData) { qWarning("QObject::moveToThread: Current thread (%p) is not the object's thread (%p)./n" "Cannot move to target thread (%p)/n", currentData->thread, d->threadData->thread, targetData->thread); return; } ......
moveToThread()的其餘工做:
生成並經過sendEvent()派發 QEvent::ThreadChange 事件
解除在當前線程中的timer註冊(在目標線程中從新註冊)
將該對象在當前事件隊列中的事件移動到目標線程的事件隊列中
...
咱們在QDialog 模態對話框與事件循環一文中提到:
調用的是QEventLoop 的 exec()
int QCoreApplication::exec() { ... QEventLoop eventLoop; int returnCode = eventLoop.exec(); ... return returnCode; }
exec()進而調用 QEventLoop::processEvents()
int QEventLoop::exec(ProcessEventsFlags flags) { Q_D(QEventLoop); ... while (!d->exit) processEvents(flags | WaitForMoreEvents | EventLoopExec); ... return d->returnCode; }
進而調用本線程內的 eventDispatcher
bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher) return false; if (flags & DeferredDeletion) QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); return d->threadData->eventDispatcher->processEvents(flags); }
前面注意這段代碼,若是沒有eventDispatcher,這個函數什麼都不作。這個東西是何時建立的呢?
QEventLoop::QEventLoop(QObject *parent) : QObject(*new QEventLoopPrivate, parent) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher) { QThreadPrivate::createEventDispatcher(d->threadData); } }
一個線程內能夠建立並啓動多個QEventLoop(事件循環能夠嵌套,你常常這樣用,只不過可能沒意識到,可考慮QEventLoop使用兩例),而第一個負責建立eventDispatcher.
QCoreApplicationn::postEvent()和線程有什麼瓜葛?
獲取接收者關聯的線程信息
將事件放入線程的事件隊列
喚醒eventDispatcher
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority) { QThreadData * volatile * pdata = &receiver->d_func()->threadData; if (data->postEventList.isEmpty() || data->postEventList.last().priority >= priority) { data->postEventList.append(QPostEvent(receiver, event, priority)); } else { QPostEventList::iterator begin = data->postEventList.begin() + data->postEventList.insertionOffset, end = data->postEventList.end(); QPostEventList::iterator at = qUpperBound(begin, end, priority); data->postEventList.insert(at, QPostEvent(receiver, event, priority)); } if (data->eventDispatcher) data->eventDispatcher->wakeUp(); ...
不管如何,事件最終都要經過 sendEvent 和 sendSpontaneousEvent 才能派發到接收的對象中
send(Spontaneous)Event直接調用notifyInternal,進而直接調用notify,最終直接調用QObject::event()
QObject::event()進而直接調用timerEvent()等事件處理函數
inline bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event) { if (event) event->spont = false; return self ? self->notifyInternal(receiver, event) : false; } inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) { if (event) event->spont = true; return self ? self->notifyInternal(receiver, event) : false; }
由於事件由其關聯的線程內的eventDispatcher進行派發,因此全部的事件處理函數都會在關聯的線程內被調用。若是關聯線程的事件循環沒有啓用呢?就不會有eventispatcher了,timerEvent等事件也就更無從談起了。
爲什麼只能在其關聯的線程內啓動timer?
在QTimer源碼分析(以Windows下實現爲例)一文中,咱們談到:
QTimer的是經過QObject的timerEvent()實現的,開啓和關閉定時器是經過QObject的startTimer()和killTimer完成的。
startTimer最終調用對象關聯線程的eventDispatcher來註冊定時器:
int QObject::startTimer(int interval) { Q_D(QObject); return d->threadData->eventDispatcher->registerTimer(interval, this);
在Win32平臺下:
void QEventDispatcherWin32::registerTimer(int timerId, int interval, QObject *object) { if (timerId < 1 || interval < 0 || !object) { qWarning("QEventDispatcherWin32::registerTimer: invalid arguments"); return; } else if (object->thread() != thread() || thread() != QThread::currentThread()) { qWarning("QObject::startTimer: timers cannot be started from another thread"); return; } ...
在Linux平臺下:
void QEventDispatcherGlib::registerTimer(int timerId, int interval, QObject *object) { #ifndef QT_NO_DEBUG if (timerId < 1 || interval < 0 || !object) { qWarning("QEventDispatcherGlib::registerTimer: invalid arguments"); return; } else if (object->thread() != thread() || thread() != QThread::currentThread()) { qWarning("QObject::startTimer: timers cannot be started from another thread"); return; } ...
在這兩個平臺下,它都會檢查當前線程和dispatcher的線程是否一致。不一致則直接返回。
爲何要這麼設計。我不太清楚。或許是由於:註冊定時器要用到回調函數,而回調函數須要在註冊的線程執行(fix me)。
使用connect鏈接信號槽時,默認是 AutoConnection
使用invokeMethod時,能夠指定 AutoConnection
設置AutoConnection就是讓Qt幫助咱們選擇直連仍是隊列鏈接的方式。選擇的依據就是當前的線程和接收者的關聯的線程是否一致,而與信號所在對象關聯的線程無關(對Qt4.8及後續版本,這句話是對的)。
這個不涉及信號的問題,處理起來很簡單:比較當前線程和接收者所關聯的線程是否一致便可。
檢查Connection的類型,處理AutoConnection
// check connection type QThread *currentThread = QThread::currentThread(); QThread *objectThread = object->thread(); if (connectionType == Qt::AutoConnection) { connectionType = currentThread == objectThread ? Qt::DirectConnection : Qt::QueuedConnection; }
對於 直連的,直接調 metacall,它進而去調用對象的 qt_metacall
if (connectionType == Qt::DirectConnection) { return QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, methodIndex, param) < 0;
對於 Queued 的鏈接,post 相應的事件,進而轉到對象的event()函數中
if (connectionType == Qt::QueuedConnection) { QCoreApplication::postEvent(object, new QMetaCallEvent(methodIndex, 0, -1, nargs, types, args));
connect中指定了AutoConnection,信號發射時,相應槽是Direct仍是Queued方式調用呢???
你應該見過兩種說法:
其一:信號關聯的線程和槽關聯的線程不一致時,則Queued方式
其二:信號發射時的當前線程和槽函數關聯的線程不一致時,則Queued方式
注意:在Qt4.7.3(包括)之前,前一種說法是對的(充分條件)。從Qt4.8開始,後面的說法是對的(充要條件)。
看看Qt4.7.3中QMetaObject::activate()的代碼:
// determine if this connection should be sent immediately or // put into the event queue if ((c->connectionType == Qt::AutoConnection && (currentThreadData != sender->d_func()->threadData || receiver->d_func()->threadData != sender->d_func()->threadData)) || (c->connectionType == Qt::QueuedConnection)) { queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv); continue; }
對比看看Qt4.8中的代碼:
const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId; // determine if this connection should be sent immediately or // put into the event queue if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)) { queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv); continue; }
Qt 源碼