- 现在的 qt 是事件驱动的,所以建议使用 自定义Worker(继承QObject) + QThread 类完成线程操作 (可在 worker 类加入定时器 QTimer)
- 所有线程操作都放在 Worker 类中(这样可以区分线程操作 和 主线程控制)
- 需要通信使用 信号和槽 解决
Worker :
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker() : QObject(nullptr)
{
FUNC_TRACE;
m_timer = new QTimer(this);
connect(m_timer,&QTimer::timeout,this,&Worker::doWork);
m_timer->setInterval(500);
}
~Worker(){
FUNC_TRACE;
}
public Q_SLOTS:
void startWork(){
FUNC_TRACE;
m_timer->start();
}
void stopWork(){
FUNC_TRACE;
m_stopRequested = true;
}
void terminateWork(){
FUNC_TRACE;
m_stopRequested = true;
m_timer->stop();
//最后尝试干净的退出
m_msgQueue.clear();//(最后的挣扎)
qDebug()<<"terminated";
emit terminated();
}
void enqueueMessage(const QString& msg){
FUNC_TRACE;
m_msgQueue.enqueue(msg);//(为什么不加锁,因为走事件队列将数据给传送过来,事件队列是线程安全的)
}
Q_SIGNALS:
void dataReady(const QString& ddd);//数据加工完成
void done();//工作结束
void terminated();//已被终结
private Q_SLOTS:
void doWork(){
FUNC_TRACE;
if(m_msgQueue.isEmpty()){
//空闲
if(m_stopRequested){
m_timer->stop();//首先停止timer,防止done连接的处理时间过长有一次进入doWork
qDebug()<<"done";
emit done();//空闲后停工
}
return;
}
//Q_ASSERT(! m_msgQueue.isEmpty());
QString sss = m_msgQueue.dequeue();
qDebug()<<"doMessageWork:"<<sss;
//拼接线程,时间信息
QString ttt = QString("thead[%1]-[%2]:%3")
.arg(reinterpret_cast<quintptr>(QThread::currentThreadId()))
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss zzz"))
.arg(sss);
emit dataReady(ttt);
}
private:
QTimer *m_timer;
bool m_stopRequested = false;
QQueue<QString> m_msgQueue;
};
Controller :
class Controller : public QObject
{
Q_OBJECT
public:
explicit Controller(QObject *parent = nullptr) : QObject(parent)
{
FUNC_TRACE;
m_thread = new QThread(this);
m_worker = new Worker();
m_worker->moveToThread(m_thread);
//connect(m_worker,&Worker::terminated, m_thread,&QThread::quit);
//connect(m_worker,&Worker::done, m_thread,&QThread::quit);
//connect(m_thread,&QThread::finished, m_worker,&Worker::deleteLater);
connect(m_worker,&Worker::done, this,&Controller::terminateThread);
connect(m_worker,&Worker::terminated, this,&Controller::terminateThread);
connect(m_worker,&Worker::dataReady, this,&Controller::onDataReady);//数据往来
connect(this,&Controller::showMessage, m_worker,&Worker::enqueueMessage);//数据往来
connect(this,&Controller::startWorker, m_worker,&Worker::startWork);
connect(this,&Controller::stopWorker, m_worker,&Worker::stopWork);
connect(this,&Controller::terminateWorker, m_worker,&Worker::terminateWork);
}
~Controller(){
FUNC_TRACE;
}
void startWorkerAsync(){
FUNC_TRACE;
m_thread->start();
emit startWorker(QPrivateSignal());
}
void stopWorkerAsync(){
FUNC_TRACE;
emit stopWorker(QPrivateSignal());
}
void terminateWorkerAsync(){
FUNC_TRACE;
emit terminateWorker(QPrivateSignal());
}
void terminateThread(){
FUNC_TRACE;
if(m_worker){
m_worker->deleteLater();
}
m_thread->quit();
if(! m_thread->wait(100)){
m_thread->terminate();
m_thread->wait(100);
}
}
bool waitForWorkerDone(int timeout){
FUNC_TRACE;
if(! m_worker){
return true;
}
bool rrr = false;
QEventLoop loop;
connect(m_worker,&Worker::done, &loop,[&rrr,&loop](){
rrr = true;
loop.quit();
});
QTimer::singleShot(timeout, &loop,&QEventLoop::quit);
loop.exec();
return rrr;
}
bool waitForWorkerTerminated(int timeout){
FUNC_TRACE;
if(! m_worker){
return true;
}
bool rrr = false;
QEventLoop loop;
connect(m_worker,&Worker::terminated, &loop,[&rrr,&loop](){
rrr = true;
loop.quit();
});
QTimer::singleShot(timeout, &loop,&QEventLoop::quit);
loop.exec();
return rrr;
}
Q_SIGNALS:
void startWorker(QPrivateSignal);//(不允许类外部调用)
void stopWorker(QPrivateSignal); //(不允许类外部调用)
void terminateWorker(QPrivateSignal);//(不允许类外部调用)
void showMessage(const QString& msg);
private Q_SLOTS:
void onDataReady(const QString& ddd){
FUNC_TRACE;
qDebug()<<QString("data-ready:%1").arg(ddd);
}
private:
QThread *m_thread;
Worker *m_worker;
};
附:
#define FUNC_TRACE \
qDebug()<<QString("[%1][%2]%3").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss zzz")).arg((quintptr)(QThread::currentThreadId())).arg(__FUNCTION__)
总结:
- woker->moveToThread() 后,所有调用需要支持多线程,不可直接调用,可使用信号槽(建议)或 加锁操作(特麻烦,特容易出现死锁)
- 利用connect时对象在不同线程时,自动使用Qt::QueuedConnection机制(参数复制一份在消息队列中) + 消息队列支持多线程,将进出参数放在信号槽中
worker
起始就几句话(其他全是处于某种目的写的)
m_timer = new QTimer(this);
connect(m_timer,&QTimer::timeout,this,&Worker::doWork);
m_timer->setInterval(500);
m_timer->start();
m_timer->stop();
controller 关键函数
控制 worker 开始,停止,也是就几句话(其余都是辅助作用)
可以将一些业务逻辑和controller合并(即controller可以是业务对象)
m_thread = new QThread(this);
m_worker = new Worker();
m_worker->moveToThread(m_thread);
connect(m_worker,&Worker::terminated, m_thread,&QThread::quit);//自动停止thread(疑问两次连接deleteLater会不会出问题)
connect(m_worker,&Worker::done, m_thread,&QThread::quit);//自动停止thread
connect(m_thread,&QThread::finished, m_worker,&Worker::deleteLater);//析构worker
connect(this,&Controller::startWorker, m_worker,&Worker::startWork);//启动
connect(this,&Controller::stopWorker, m_worker,&Worker::stopWork);//停止
connect(this,&Controller::terminateWorker, m_worker,&Worker::terminateWork);//强制终止
connect(this,&Controller::showMessage, m_worker,&Worker::enqueueMessage);//传入数据
m_thread->start();//启动线程事件循环 (先启动循环,大不了空跑几圈)
emit startWorker(QPrivateSignal());//启动worker(其实是启动timer向thread消息队列中丢timeout消息)
emit stopWorker(QPrivateSignal());
emit terminateWorker(QPrivateSignal());
if(m_worker){
m_worker->deleteLater();//(有疑问,不太清楚可不可行,我觉得行)
}
m_thread->quit();//退出事件循环
if(! m_thread->wait(100)){
m_thread->terminate();//退出失败 --> 终结thread
m_thread->wait(100);//(因为是最后的手段,返回值已经不重要了,失败我也没法了)
}
测试使用
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Controller *controller = new Controller();
int count = 0;//计数
QTimer *timer = new QTimer();
QObject::connect(timer,&QTimer::timeout, controller, [controller,timer,&count](){
controller->showMessage(QString::number(count++));//[0,10]
if(count>10){
timer->stop();
controller->stopWorkerAsync();
if(! controller->waitForWorkerDone(100)){
controller->terminateWorkerAsync();
//停止失败 --> 强制停止worker
if(! controller->waitForWorkerTerminated(10)){
//强停失败 --> 终结线程执行(最后的手段)
controller->terminateThread();
}
}
}
});
timer->start(600);
controller->startWorkerAsync();
//delete controller;
return a.exec();
}