使用QThread有两种方式:
- 继承重载run()
- movetothread()
由于run()是工作线程的入口,所以前者比较容易理解。
着这片文章中,我尝试第二种方法的工作方式。
事件循环
作为一个事件驱动的编程框架,qt广泛使用事件循环。例如,一下函数背英语几乎所有的qt项目:
QCoreApplication::exec()
QDialog::exec()
QDrag::exec()
QMenu::exec()
QThread::exec()
...
他们每一个都会创建并运行一个QEventLoop 对象,拿QCoreApplication举例:
int QCoreApplication::exec()
{
//...
QEventLoop eventLoop;
int returnCode = eventLoop.exec();
//...
return returnCode;
}
概念化描述如下:
int QEventLoop::exec(ProcessEventsFlags flags)
{
//...
while (!d->exit) {
while (!posted_event_queue_is_empty) {
process_next_posted_event();
}
}
//...
}
每一个宪政拥有他自己的事件队列,特别是:事件队列属于线程,而不属于事件循环,他被运行在这个现场的所有事件循环共享。
当事件循环发现事件队列不为空,他会处理一个接一个处理这些事件。最终,目标对象的QObject::event()成员被调用。
看起来,没有一个例子作为佐证,我们很难理解时间系统是怎么工作,接下来,看一个而demo。
example
在这个例子中:
第一步:
创建自定义事件
new QEvent(QEvent::User)
把事件post队列
QCoreApplication::postEvent()
然后,
事件循环发现这个事件
QApplication::exec()
事件循环调用
Test::event()
#if QT_VERSION>=0x050000
#include <QtWidgets>
#else
#include <QtGui>
#endif
class Test : public QObject
{
Q_OBJECT
protected:
bool event(QEvent *evt)
{
if (evt->type() == QEvent::User) {
qDebug()<<"Event received in thread"<<QThread::currentThread();
return true;
}
return QObject::event(evt);
}
};
class Button : public QPushButton
{
Q_OBJECT
Test *m_test;
public:
Button(Test *test):QPushButton("Send Event"), m_test(test)
{
connect(this, SIGNAL(clicked()), SLOT(onClicked()));
}
private slots:
void onClicked()
{
QCoreApplication::postEvent(m_test, new QEvent(QEvent::User));
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThread();
Test test;
Button btn(&test);
btn.show();
return a.exec();
}
这个例子中,Test::event() 在主线程被调用。
如果我们想让他在工作线程中执行,要怎么办?
Thread Affinity(线程关联)
由于每个线程都有自己的事件队列,那么,在一个多线程应用中,就会有多个事件队列。问题来了,我们如何要把事件post到那个队列中?
看一下postEvent()的代码:
void QCoreApplication::postEvent(QObject *receiver, QEvent *event)
{
QThreadData * volatile * pdata = &receiver->d_func()->threadData;
QThreadData *data = *pdata;
QMutexLocker locker(&data->postEventList.mutex);
data->postEventList.addEvent(QPostEvent(receiver, event));
}
可以看出,事件队列包含在 receiver 的线程额属性值。这个线程被称为线程相关(这个对象所在的线程)。通常,这个线程就是创建对象的线程,但是,QObject::moveToThread() 可以改变之。
请注意,由于QMutex的存在,QCoreApplication::postEvent()是线程安全的。
现在, it’s easy to run the event process it worker thread instead of main thread.
举例:
在主函数添加三行代码:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThread();
Test test;
QThread thread; //new line
test.moveToThread(&thread); //new line
thread.start(); //new line
Button btn(&test);
btn.show();
return a.exec();
}
输出结果:
From main thread: QThread(0x9e8100)
Event received in thread QThread(0x13fed4)
Event received in thread QThread(0x13fed4)
不添加那三行的输出结果如下:
From main thread: QThread(0x9e8100)
Event received in thread QThread(0x9e8100)
Event received in thread QTh
read(0x9e8100)
Queued Connection
对于Queued Connection来说,当信号被通知,事件就会被post到事件队列。
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs, types, args);
QCoreApplication::postEvent(c->receiver, ev);
然后,这个事件就会被事件队列发现,最终在这个线程调用QObject::event()。
bool QObject::event(QEvent *e)
{
switch (e->type()) {
case QEvent::MetaCall:
{
QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e);
由于QCoreApplication::postEvent()线程安全,因此如果线程间用queued signal/slot connections的方式相互影响的时候,不需要考虑多线程安全防范措施。
引用:
http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-2/