简述
前面,我们介绍了QThread常用的两种方式:
- worker-object
- 子类化QThread
下面,我们首先来看看子类化QThread在日常中的应用。
- 简述
- 子类化QThread
- 在主线程中更新UI
- 正常结束线程
- 更多参考
一般情况下,QThread进行耗时操作的同时会与UI进行交互,比如:显示进度、旋转等待。。。进行友好型的交互,让用户知道当前的操作。
子类化QThread
我们以更新进度条为例,来模拟一个耗时操作,并分享我们有可能在此过程中遇到的问题及解决办法。
首先,我们定义一个WorkerThread 类,让其继承自QThread,并重写run()函数,每隔50毫秒更新下当前的值,然后发射resultReady()信号(用于更新进度条)。
#include <QThread>
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = 0)
: QThread(parent)
{
qDebug() << "Worker Thread : " << QThread::currentThreadId();
}
protected:
virtual void run() Q_DECL_OVERRIDE {
qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
int nValue = 0;
while (nValue < 100)
{
// 休眠50毫秒
msleep(50);
++nValue;
// 准备更新
emit resultReady(nValue);
}
}
signals:
void resultReady(int value);
};
构建一个主界面,用于显示进度条。
class MainWindow : public CustomWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0)
: CustomWindow(parent)
{
qDebug() << "Main Thread : " << QThread::currentThreadId();
// 创建开始按钮、进度条
QPushButton *pStartButton = new QPushButton(this);
m_pProgressBar = new QProgressBar(this);
//设置文本、进度条取值范围
pStartButton->setText(QString::fromLocal8Bit("开始"));
m_pProgressBar->setFixedHeight(25);
m_pProgressBar->setRange(0, 100);
m_pProgressBar->setValue(0);
QVBoxLayout *pLayout = new QVBoxLayout();
pLayout->addWidget(pStartButton, 0, Qt::AlignHCenter);
pLayout->addWidget(m_pProgressBar);
pLayout->setSpacing(50);
pLayout->setContentsMargins(10, 10, 10, 10);
setLayout(pLayout);
// 连接信号槽
connect(pStartButton, SIGNAL(clicked(bool)), this, SLOT(startThread()));
}
~MainWindow(){}
private slots:
// 更新进度
void handleResults(int value)
{
qDebug() << "Handle Thread : " << QThread::currentThreadId();
m_pProgressBar->setValue(value);
}
// 开启线程
void startThread()
{
WorkerThread *workerThread = new WorkerThread(this);
connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
// 线程结束后,自动销毁
connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater()));
workerThread->start();
}
private:
QProgressBar *m_pProgressBar;
WorkerThread m_workerThread;
};
显然,主线程、Worker构造所在的线程、槽函数处于同一线程,而run()函数处于其它线程。
Main Thread : 0x34fc
Worker Thread : 0x34fc
Worker Run Thread : 0x4038
Handle Thread : 0x34fc
在主线程中更新UI
当连接方式改为“Qt::DirectConnection”时:
connect(workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)), Qt::DirectConnection);
会出现以下错误:
ASSERT failure in QCoreApplication::sendEvent: “Cannot send events to objects owned by a different thread. Current thread e346e8. Receiver customWidget’ (of type ‘MainWindow’) was created in thread 4186a0”, file kernel\qcoreapplication.cpp, line 553
这时,线程的运行情况如下所示:
Main Thread : 0x2c6c
Worker Thread : 0x2c6c
Worker Run Thread : 0x4704
Handle Thread : 0x4704
也就是说,run()函数与槽函数所在的是同一个线程。
根据Qt之Threads和QObjects中“跨线程的信号和槽”部分,提到的“Direct Connection:当信号发射后,槽函数立即被调用。槽函数在信号发射者所在的线程中执行,而未必需要在接收者的线程中。”
所以,不难理解,因为我们在子线程中更新了UI。
正常结束线程
上述代码有一些问题,都是我们经常遇到的。
- 多次connect信号与槽:当多次点击“开始”按钮的时候,就会启动多个线程,同时更新进度条。
- 暴力终止线程:而且当用户关闭窗体的时候,线程有可能还在运行,这时极有可能发生未知错误。
正如前面提到过terminate(),比较危险,不鼓励使用。线程可以在代码执行的任何点被终止。线程可能在更新数据时被终止,从而没有机会来清理自己,解锁等等。。。总之,只有在绝对必要时使用此函数。
举一个简单的例子:当你的哥们正在酣睡时,你突然泼了一盆凉水,想象一下后果?O(∩_∩)O哈哈~
为了避免以上问题,我们先修改如下:
class MainWindow : public CustomWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0)
: CustomWindow(parent)
{
// ...
connect(&m_workerThread, SIGNAL(resultReady(int)), this, SLOT(handleResults(int)));
}
~MainWindow(){}
private slots:
// ...
void startThread()
{
if (!m_workerThread.isRunning())
m_workerThread.start();
}
private:
WorkerThread m_workerThread;
};
这时,不会出现启动多个线程的问题。但是如果线程正在运行,我们就关闭主界面,则会出现另外一个很常见的问题:
QThread: Destroyed while thread is still running
这是因为次线程还在运行,就结束了UI主线程。这个问题在耗时操作中经常遇到,我们可以采用以下方式来避免:
- QMutex互斥锁 + bool成员变量。
#include <QThread>
#include <QMutexLocker>
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = 0)
: QThread(parent),
m_bStopped(false)
{
qDebug() << "Worker Thread : " << QThread::currentThreadId();
}
~WorkerThread()
{
stop();
quit();
wait();
}
void stop()
{
QMutexLocker locker(&m_mutex);
m_bStopped = true;
}
protected:
virtual void run() Q_DECL_OVERRIDE {
qDebug() << "Worker Run Thread : " << QThread::currentThreadId();
int nValue = 0;
while (nValue < 100)
{
// 休眠50毫秒
msleep(50);
++nValue;
// 准备更新
emit resultReady(nValue);
// 检测是否停止
{
QMutexLocker locker(&m_mutex);
if (m_bStopped)
break;
}
// locker超出范围并释放互斥锁
}
}
signals:
void resultReady(int value);
private:
bool m_bStopped;
QMutex m_mutex;
};
- requestInterruption() + isInterruptionRequested()
这两个接口是Qt5以后引入的,使用很方便:
class WorkerThread : public QThread
{
Q_OBJECT
public:
explicit WorkerThread(QObject *parent = 0)
: QThread(parent)
{
}
~WorkerThread() {
// 请求终止
requestInterruption();
quit();
wait();
}
protected:
virtual void run() Q_DECL_OVERRIDE {
// 是否请求终止
while (!isInterruptionRequested())
{
// 耗时操作
}
}
};
在耗时操作中使用isInterruptionRequested()来判断是否请求终止线程,如果没有,则一直运行;当希望终止线程的时候,调用requestInterruption()即可。