原文地址:http://blog.csdn.net/dbzhang800/article/details/6557272
原文发布时间:2011-06-20 22:09
注意,本文试图通过源码解释下面的问题:
- 子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; }
对比看看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 源码
相关推荐
QT 利用继承Qobject实现多线程 利用moveToThread()函数将所需在新线程中处理的内容转移到开辟的新线程中 此外还利用了对不同线程进行加锁
Qt 多线程采用继承QObject方式编写、
实现QObject与JavaScript通讯(基于QWebEngine + QWebChannel)
采用Qt5.9和VS2015的环境,编写了一个多线程的小程序,线程的创建方式继承QObject。
qtThread_2.rar qt 线程练习样例代码,继承Qobject方式
以文件复制为例将继承QThread、QObject,两种Qt多线程方式作简单对比
QObject调用moveToThread(QThread *targetThread)后, 运行targetThread.start(),此后QObject的事件将在targetThread中处理。 比如:QObject的槽函数,将在targetThread中处理。
一个不带QObject依赖的Qt信号系统的纯Python实现
使用Qt也已经有一段时间了,虽然使用过继承QThread重写run函数,以及继承QObject然后使用MoveToThread两种方法实现多线程,但是在QSerialPort的使用过程中,两种方法都存在一定的问题。 典型的问题: QObject: ...
Qt Creator 多线程读取文件到程序显示 利用QT Creator多任务读取一个文档到程序里 为了防止直接读取文件里的内容太大而发生卡顿,于是多线程读取将更高效的解决这个问题。 效果图如下: 其中pro文件无需改动,...
qt多线程使用,推荐用法,继承QObject,使用worktothread方法。
本示例程序使用Qt Creator创建多线程示例,下载下来可直接编译运行: 1.继承QObject 2.继承QThread 3.继承QObject魔改 教程地址: https://tangxing.blog.csdn.net/article/details/111615381
创建线程有两种办法,一种继承QThread重载run, 一种继承QObject用moveToThread,详细代码比较
在次线程中输出系统时间,传递参数到str_time中,发出信号在主界面lineedit里显示系统时间
简述了QObject的源代码,对初学者也能看懂
QT_CTP_MD接口的例子,包括了初始化,登录,订阅,还有非QOBJECT类和主UI之间的通信方法
不会出现QObject: Cannot create children for a parent that is in a different thread
Qt分析QObject子类内部成员结构,分控制台和UI界面两种方式,代码比较简单,适合新手。
在我开发的系统,需要子线程去运行,然后把运行的结果发给UI线程,让UI线程知道运行的进度。 首先创建线程很简单 def newThread(self): d = Data() print '子线程的运行' t1 = threading.Thread(target=newThread...
被绑定到某个Qthread上的Qobject对象,其信号-槽事件循环由该线程负责。这样,便可方便的指定某个套接字对象使用的线程。同样,受惠于Qt的良好封装,直接支持Tcp套接字及SSL套接字,且在运行时可动态调整。(注:...