您的位置:首页 > 移动开发 > Objective-C

QObject 之 Thread依附性

2014-03-30 16:26 239 查看
引自:http://blog.csdn.net/dbzhang800/article/details/6557272

非常感谢作者:dbzhang800

注意,本文试图通过源码解释下面的问题:
子QObject必须在其parent关联的线程内创建
调用moveToThread()的对象其parent必须为0
事件驱动的对象要在单一线程内使用
QTimer、network模块的QTcpSocket等等
为什么不能在非关联线程内开启QTimer或者连接QTcpSocket到服务器?

删除QThread对象前,确保线程内所有对象都没销毁

AutoConnection的是是非非,两种说法孰是孰非?
其一:信号关联的线程和槽关联的线程不一致时,则Queued方式
其二:信号发射时的当前线程和槽函数关联的线程不一致时,则Queued方式

但很显然,我没做到这一点(能力所限,现阶段我只能让自己勉强明白),尽管如此,本文应该还是提供了很多你理解这些问题所需的背景知识。


QObject的线程关联性

线程关联性(Thread Affinity)???

什么东西?
每一个QObject都会和一个线程相关联
QObject 是线程感知的,每一个QObject及派生类的对象被创建时都会将其所在线程的引用保存下来(可以通过QObject::thread()返回)。
干嘛用的?
用于事件系统
QObject对象的事件处理函数始终要在其所关联线程的上下文中执行。
可否改变?
 使用QObject::moveToThread()可以将QObject对象从一个线程移动到另一个线程。


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注册(在目标线程中重新注册)
将该对象在当前事件队列中的事件移动到目标线程的事件队列中
...


事件循环


QCoreApplication::exec()

我们在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.


QCoreApplication::postEvent()

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等事件也就更无从谈起了。


QTimer疑问?

为何只能在其关联的线程内启动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)。


Qt::AutoConnection

使用connect连接信号槽时,默认是 AutoConnection

使用invokeMethod时,可以指定 AutoConnection

设置AutoConnection就是让Qt帮助我们选择直连还是队列连接的方式。选择的依据就是当前的线程和接收者的关联的线程是否一致,而与信号所在对象关联的线程无关 (对Qt4.8及后续版本,这句话是对的)。


invokeMethod

这个不涉及信号的问题,处理起来很简单:比较当前线程和接收者所关联的线程是否一致即可。

检查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

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;

a6d8
}


对比看看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 源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程 object