转载: http://www.cnblogs.com/liushui-sky/p/5829563.html
背景描述:
以前,继承 QThread 重新实现 run() 函数是使用 QThread唯一推荐的使用方法。这是相当直观和易于使用的。但是在工作线程中使用槽机制和Qt事件循环时,一些用户使用错了。Qt 核心开发人员Bradley T. Hughes, 推荐使用QObject::moveToThread 把它们移动到线程中。不幸的是, 以用户反对这样使用。Olivier Goffart, 前Qt 核心开发人之一, 告诉这些用户你们不这样做就错了。最终这俩种用法我们都在QThread的文档中发现 。
QThread::run() 是线程的入口点
从Qt文档中我们可以看到以下内容:
A QThread instance represents a thread and provides the means to start() a thread,
which will then execute the reimplementation of QThread::run().
The run() implementation is for a thread what the main() entry point is for the application.
Usage 1-0
在新的线程中执行一些代码,继承QThread 重新实现 run()函数接口。
For example
#include <QtCore>
class Thread : public QThread
{
private:
void run()
{
qDebug()<<"From worker thread: "<<currentThreadId();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
Thread t;
QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));
t.start();
return a.exec();
}
结果输出如下:
From main thread: 0x15a8
From worker thread: 0x128c
Usage 1-1
正因QThread::run() 是线程的入口, 所以很容易的理解它们, 并不是所有的代码都在run()接口中被直接调用而不在工作线程中被执行。
接下来的例子中,成员变量 m_stop
在 stop() 和 run()都可被访问到。考虑到前者将在主线程执行,后者在工作线程执行,互斥锁或其它操作是有必要的。
#if QT_VERSION>=0x050000
#include <QtWidgets>
#else
#include <QtGui>
#endif
class Thread : public QThread
{
Q_OBJECT
public:
Thread():m_stop(false)
{}
public slots:
void stop()
{
qDebug()<<"Thread::stop called from main thread: "<<currentThreadId();
QMutexLocker locker(&m_mutex);
m_stop=true;
}
private:
QMutex m_mutex;
bool m_stop;
void run()
{
qDebug()<<"From worker thread: "<<currentThreadId();
while (1) {
{
QMutexLocker locker(&m_mutex);
if (m_stop) break;
}
msleep(10);
}
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
QPushButton btn("Stop Thread");
Thread t;
QObject::connect(&btn, SIGNAL(clicked()), &t, SLOT(stop()));
QObject::connect(&t, SIGNAL(finished()), &a, SLOT(quit()));
t.start();
btn.show();
return a.exec();
}
结果输出如下:
From main thread: 0x13a8
From worker thread: 0xab8
Thread::stop called from main thread: 0x13a8
你可以看到Thread::stop() 函数是在主线程中被执行的。
Usage 1-2 (错误的使用方式)
以上的例子很容易明白,但它不是那么直观当事件系统(或队列)中引进工作线程时。
例子如下, 我们应该做些什么,如果我们想在工作线程中周期性的做些动作?
- 在Thread::run()中创建一个QTimer
- 将超时信号连接到线程中的槽函数上
#include <QtCore>
class Thread : public QThread
{
Q_OBJECT
private slots:
void onTimeout()
{
qDebug()<<"Thread::onTimeout get called from? : "<<QThread::currentThreadId();
}
private:
void run()
{
qDebug()<<"From worker thread: "<<currentThreadId();
QTimer timer;
connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
timer.start(1000);
exec();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
Thread t;
t.start();
return a.exec();
}
乍看起来代码没什么问题。当线程开始执行时, 我们在当前线程的事件处理中启动了一个定时器。我们将 onTimeout()
连接到超时信号上。此时我们希望它在线程中执行?
但是执行结果如下:
From main thread: 0x13a4
From worker thread: 0x1330
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Thread::onTimeout get called from?: 0x13a4
Oh, No!!! 它为什么在主线程中被调用了!
是不是很有趣?(接下来我们将要讨论这是为什么)
如何解决这个问题
为了使槽函数工作在线程中, 有人尝试在connect()函数中传入参数 Qt::DirectConnection
connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()), Qt::DirectConnection);
还有人尝试在线程构造函数中添加如下功能。
moveToThread(this)
它们都会如期望的工作吗. 但是 …
第二个用法的是错误的,
尽管看起来它工作啦,但很令人费解,这不是QThread 设计的本意(QThread 中所有实现的函数是被创建它的线程来调用的,不是在线程中)
实际上,根据以上表述,第一个方案是错误的。onTimeout() 是线程对象的一个成员函数,会被创建它的线程来调用。
它们都是错误的使用方法?!我们该如何做呢?
Usage 1-3
因为没有一个线程类的成员是设计来被该线程调用的。所以如果我们想使用槽函数必须创建一个独立的工作对象。
#include <QtCore>
class Worker : public QObject
{
Q_OBJECT
private slots:
void onTimeout()
{
qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
}
};
class Thread : public QThread
{
Q_OBJECT
private:
void run()
{
qDebug()<<"From work thread: "<<currentThreadId();
QTimer timer;
Worker worker;
connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
timer.start(1000);
exec();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
Thread t;
t.start();
return a.exec();
}
结果如下:
From main thread: 0x810
From work thread: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
Worker::onTimeout get called from?: 0xfac
问题解决啦!
尽管运行的很好,但是你会注意到,当工作线程中运行在事件循环 QThread::exec()
中时,在QThread::run() 函数接口中没有执行自身相关的事务。
所以我们是否可以将对象的创建从QThread::run()中移出, 此时, 槽函数是否依旧会被QThread::run()调用?
Usage 2-0
如果我们只想使用QThread::exec(), 默认情况下会被QThread::run() 调用, 那不需要子类化QThread。
- 创建一个工作对象
- 建立信号与槽的连接
- 将工作对象移至子线程中
- 启动线程
#include <QtCore>
#include <QUdpSocket>
class Worker : public QObject
{
Q_OBJECT
public:
void Work()
{
}
private:
QUdpSocket *socket;
private slots:
void onTimeout()
{
qDebug()<<"Worker::onTimeout get called from?: "<<QThread::currentThreadId();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
QThread t;
QTimer timer;
Worker worker;
QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
timer.start(1000);
timer.moveToThread(&t);
worker.moveToThread(&t);
t.start();
return a.exec();
}
结果是:
From main thread: 0x1310
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
Worker::onTimeout get called from?: 0x121c
正如所预料的,槽函数没有在主线程中执行。
在此例中,定时器和工作对象都被移至子线程中,实际上,将定时器移至子线程中是没有必要的。
Usage 2-1
在上面的例子当中将 timer.moveToThread(&t);
这一行注释掉。
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"From main thread: "<<QThread::currentThreadId();
QThread t;
QTimer timer;
Worker worker;
QObject::connect(&timer, SIGNAL(timeout()), &worker, SLOT(onTimeout()));
timer.start(1000);
// timer.moveToThread(&t);
worker.moveToThread(&t);
t.start();
return a.exec();
}
不同之处如下:
在上面的例子中,
- 信号
timeout()
是由子线程发送出去的。 - 定时器和工作对象是在同一线程当中的,它们的连接方式直接连接。
- 槽函数的调用和信号的触发是在同一线程中的。
在本例中,
- 信号
timeout()
是由主线程发出的。 - 定时器和工作对象是在不同的线程中,它们的连接方式队列连接。
- 槽函数是在子线程中被调用。
- 由于队列连接机制的存在,可以安全的将信号和槽函数在不同的线程中连接起来。如果所有的跨线程通信都通过队列连接方式,那么多线程的互斥防范机制将不在需要。
总结
- 子类化QThread ,实现 run()函数接口是很直观的,也存在很多不错的方法去子类化QThread,但是在工作线程中处理事件处理并不是一件简单的事。
- 在事件处理存在时使用对象时将它们移至线程中时很简单的,这样将事件处理和队列连接的细节给隐藏了。
原文:http://blog.debao.me/2013/08/how-to-use-qthread-in-the-right-way-part-1/
转自:https://blog.csdn.net/zhenwo123/article/details/40861171