在Qt项目中,不可避免的会使用到线程,而在Qt的官方文档中,只有继承QThread重写run函数的用法。
在实际应用中,发现该用法不够灵活,虽然使用run也能实现项目要求,但代码量却要多很多,而且不可避免的会破坏类的封装性,因为一个thread只能做一件事情。
并且,对于继承自QThread类的用法,thread(此时函数工作在线程对象中)对象依旧会有调用线程创建(通常都是主线程),而只有run函数中的内容才在子线程中执行,对于有强迫症的人来说,这是难以接受的。并且这种写法,有的时候会带来线程间对象调用和槽机制的异常等诸多问题。
所以,推荐使用如下方法来优雅的使用线程:
1、在调用线程中(通常是主线程)创建一个工作对象QWorker(执行想要做的事情);
2、在调用线程中(通常是主线程)创建一个QThread对象;
3、调用worker->moveToThread方法,将worker关联到thread对象中。
需要注意的是,thread对象是线程的管理对象,其必须存在与它的调用线程中,不能将其move,反正也move不成功的。
代码大致如下,copy肯定是运行不了的,仅供参考:
1、先是工作对象:
定义:
#ifndef QWORKER_H
#define QWORKER_H
#include <QObject>
class QTime;
class QTimer;
class qDebug;
class QThread;
class QWorker : public QObject
{
Q_OBJECT
public:
explicit QWorker(QObject *parent = 0);
~QWorker();
signals:
void SignalElapsed(int iElapsed);
void SignalCnt(int iCnt);
public slots:
void Start(int iTimer,int iTotal);
void Stop();
void Exec();
private:
QTimer *m_pTimer;
int m_iTotal;
int m_iCnt;
int m_iExec;
QTime m_oTime;
bool m_bFlag;
};
#endif // QWORKER_H
实现:
#include <QTime>
#include <QTimer>
#include <QDebug>
#include <QThread>
#include "QWorker.h"
QWorker::QWorker(QObject *parent) :
QObject(parent)
{
qDebug() << "worker QWorker at " << QThread::currentThreadId();
m_pTimer = NULL;
m_iTotal = 0;
m_iCnt = 0;
m_iExec = 0;
m_pTime = NULL;
m_bFlag = false;
m_pTimer = new QTimer(this);
}
QWorker::~QWorker()
{
qDebug() << "worker ~QWorker at " << QThread::currentThreadId();
}
void QWorker::Start(int iTimer, int iTotal)
{
qDebug() << "worker start at " << QThread::currentThreadId();
if(NULL != m_pTimer)
{
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(Exec()));
m_pTimer->start(iTimer);
}
if(NULL != m_pTime)
{
m_pTime->start();
}
m_iTotal = iTotal;
}
void QWorker::Stop()
{
qDebug() << "worker stop at " << QThread::currentThreadId();
m_iTotal = 0;
m_iCnt = 0;
m_iExec = 0;
}
void QWorker::Exec()
{
if(!m_bFlag)
{
qDebug() << "worker exec at " << QThread::currentThreadId();
m_bFlag = true;
}
if(NULL != m_oTime)
{
int iElapsed = m_oTime.elapsed();
emit SignalElapsed(iElapsed);
}
while((m_iExec++) < m_iTotal)
{
m_iCnt++;
}
m_iExec = 0;
emit SignalCnt(m_iCnt);
if(NULL != m_oTime)
{
m_oTime.restart();
}
}
2、然后是在主线中,使用线程和工作对象:
定义:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class QThread;
class QWorker;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
signals:
void SignalStart(int iTimer,int iTotal);
void SignalStop();
private slots:
void OnCnt(int iCnt);
void OnElapsed(int iElapsed);
void on_m_pBtnStart_clicked();
void on_m_pBtnStop_clicked();
private:
Ui::MainWindow *ui;
QThread *m_pThread;
QWorker *m_pWorker;
int m_iLastElapsed;
};
#endif // MAINWINDOW_H
实现:
#include <QThread>
#include "QWorker.h"
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
qDebug() << "window MainWindow at " << QThread::currentThreadId();
ui->setupUi(this);
m_pThread = NULL;
m_pWorker = NULL;
m_iLastElapsed = 0;
m_pThread = new QThread(this);
m_pWorker = new QWorker();
if(NULL != m_pThread && NULL != m_pWorker)
{
connect(m_pWorker,SIGNAL(SignalCnt(int)),this,SLOT(OnCnt(int)));
connect(m_pWorker,SIGNAL(SignalElapsed(int)),this,SLOT(OnElapsed(int)));
connect(this,SIGNAL(SignalStart(int,int)),m_pWorker,SLOT(Start(int,int)));
connect(this,SIGNAL(SignalStop()),m_pWorker,SLOT(Stop()));
m_pWorker->moveToThread(m_pThread);
}
}
MainWindow::~MainWindow()
{
qDebug() << "window ~MainWindow at " << QThread::currentThreadId();
delete ui;
}
void MainWindow::on_m_pBtnStart_clicked()
{
if(NULL != m_pThread && NULL != m_pWorker)
{
qDebug() << "window start at " << QThread::currentThreadId();
m_pThread->start();//必须先启动线程事件驱动,否则无法接收信号
emit SignalStart(ui->m_pEdtTimer->text().toInt(),ui->m_pEdtCnt->text().toInt());
}
}
void MainWindow::on_m_pBtnStop_clicked()
{
qDebug() << "window stop at " << QThread::currentThreadId();
emit SignalStop();
if(NULL != m_pThread)
{
m_pThread->wait(10);
m_pThread->quit();
m_pThread->wait(10);
}
}
void MainWindow::OnCnt(int iCnt)
{
ui->m_pLabel1->setText(QString::number(iCnt));
}
void MainWindow::OnElapsed(int iElapsed)
{
ui->m_pLabel2->setText(QString::number(iElapsed));
if(m_iLastElapsed != iElapsed)
{
m_iLastElapsed = iElapsed;
ui->m_pLabel3->setText(QString::number(m_iLastElapsed));
}
}