【问题标题】:When is QThread quit() or finished()?QThread 何时退出()或完成()?
【发布时间】:2014-04-28 19:10:12
【问题描述】:

我是 Qt 新手,只想在 Qt GUI 中显示视频。除了处理 QThread 的一些细节,我基本上都搞清楚了,这真的很烦人。

我将我的问题重新表述为一个更简单的程序,希望它能更好地解释

首先我定义了这个 QObject 类

#include <QObject>
#include <QDebug>
class myObject : public QObject
{
    Q_OBJECT
public:
    explicit myObject(QObject *parent = 0);
    bool stop;

signals:
    void finishWork();

public slots:
    void dowork();
    void onfinishThread();

};

myObject::myObject(QObject *parent) :
QObject(parent)
{
    stop = true;
}

void myObject::dowork(){
    qDebug()<<"start working!";
    while(!stop){
        qDebug()<<"working...";
    }
    emit finishWork();
    qDebug()<<"finish do work!";
}
void myObject::onfinishThread(){
    qDebug()<<"thread is finished!";
}

然后是主函数

#include <QCoreApplication>
#include <QThread>

#include <iostream>
#include <windows.h>

#include "myobject.h"

using namespace std;
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    myObject* myObj = new myObject();
    QThread* myThread = new QThread;

    myThread->connect(myObj, SIGNAL(finishWork()), SLOT(quit()));
    myObj->connect(myThread, SIGNAL(started()), SLOT(dowork()));
    myObj->connect(myThread, SIGNAL(finished()), SLOT(onfinishThread()));
    myObj->moveToThread(myThread);

    myObj->stop = false;

    myThread->start();

    cout<<"Press ENTER to continue....."<<endl<<endl;
    cin.ignore(1);

    myObj->stop = true;
    Sleep(10);

    if(myThread->isRunning()){
        qDebug()<<"The thread is still running?!!!";
    }

/*
    myObj->stop = false;
    Sleep(1000);
    myThread->start();
    myObj->stop = true;
*/

    myObj->deleteLater();
    myThread->deleteLater();
    return a.exec();
}

如你所见,我什至用cin尝试让dowork()先运行,但是根本没有用,输出是

所以我真的很困惑 QThread 的调度是如何工作的......

另外,如果您取消注释该部分

/*
    myObj->stop = false;
    Sleep(1000);
    myThread->start();
    myObj->stop = true;
*/

输出完全一样!打印后只停留一段时间

线程还在运行吗?!!!

有人能帮我解决这个问题吗?非常感谢。您可以简单地复制所有代码并自己测试。


--------------原始问题,不好的解释,请忽略....... --------------------------------------

我做了一个videoObj类,只有一个函数来查询帧,函数定义为:

void videoObj::ProcessFrame(){
bool getframe;
qDebug()<<"get in ProcessFrame";
while(!stop_flag){
    getframe = capture_.read(imgOriginal_);
    if(!getframe){
        qDebug()<<"Video End!";
        break;
    }
    cv::cvtColor(imgOriginal_, imgOriginal_, CV_BGR2RGB);
    QImage qimgOriginal((uchar*)imgOriginal_.data, imgOriginal_.cols, imgOriginal_.rows, imgOriginal_.step, QImage::Format_RGB888);

    emit imgProcessed(qimgOriginal);
    this->thread()->msleep(10);
    //qDebug()<<"processing frames";
}

emit stopProcess();
qDebug()<<"Stop thread";
}

基本上上面的代码只是查询帧,只要有一个就会发出

SIGNAL imgProcessed(qimgOriginal)

并且每当设置了 stop_flag 时,停止 while 循环并发出

SIGNAL stopProcess()

我在 MainWindow 类中使用这个类,这是我在构造函数中定义连接的方式:

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

video_obj_ = new videoObj();
video_thread_ = new QThread;
this->connect(video_obj_, SIGNAL(imgProcessed(QImage)), SLOT(onImgProcssed(QImage))); \\this is for displaying the image
video_obj_->connect(video_thread_, SIGNAL(started()), SLOT(ProcessFrame()));
video_obj_->moveToThread(video_thread_);
video_thread_->connect(video_obj_, SIGNAL(stopProcess()), SLOT(quit()));
}

以上代码在框架查询中运行良好。我不明白的问题是,如果我在任何 MainWiow 成员函数中设置 video_obj_->stop_flag,videoObj 类中的 ProcessFrame() 应该发出 stopProcess() 信号并触发 video_thread_ 的 quit(),然后线程应该完成,即 video_thread_->finished() 为真。

但是,如果我这样做:

connect(video_thread_, SIGNAL(finished()), this, SLOT(onStopProcess())); //onStopProcess() see below

void MainWindow::on_btnStart_clicked()
{
video_obj_->stop_flag = 1;
this->thread()->msleep(10);

video_obj_->capture_.open(ui->lineEditVidoeAddress->text().toStdString());
//... something about videoCapture setting here, not related

video_obj_->capture_.set(CV_CAP_PROP_POS_FRAMES, 0);
video_obj_->stop_flag = 0;
this->thread()->msleep(10);
if(video_thread_->isRunning()){
    qDebug()<<"The thread is still running?!!!";
}
video_thread_->start();
}

void MainWindow::onStopProcess(){
    qDebug()<<"thread is finished";
}

它会给我输出:

Stop thread 
The thread is still running?!!!  
thread is finished 

也就是说触发quit()不是线程结束,或者quit()还没有被触发。

如果我使用:

video_thread_->wait(); //instead of video_thread_->wait(10);

程序将冻结。

有没有人可以帮我解决这个问题,这让我对这个 quit() 和 finished() 感到很困惑...... 谢谢!

【问题讨论】:

  • 你确定你开始你的话题了吗?我没有注意到你使用 QThread::start() 函数。
  • 既然打印了“停止线程”,它应该已经被调用但没有粘贴在这里。
  • 汉克,是的,我做到了。抱歉,我才意识到我没有把代码的开头部分放在...我更新了我的帖子

标签: qt qthread


【解决方案1】:

由于调用stop_flag=1,video_thread_完成当前函数

ProcessFrame

ProcessFrame 完成之前,通过 emit stopProcess()video_thread_ 上调用 quit。

但是,quit 和 terminate 不同,terminate 可以随时退出线程(但并不安全),quit 与事件循环一起工作,如果线程没有事件循环,则退出无效。

我猜在qthread执行eventloop中的下一个事件之前,它会检查一些标志,可以通过quit设置,如果标志是通过quit设置的,那么它不会执行eventloop中的下一个事件。也可以是在eventloop中插入了一个quit事件,eventloop中的下一个事件就会退出。

stop_flag = 1之后,你调用了video_thread_->wait,这会阻止video_thread_执行eventloop中的下一个事件,因此退出不会生效然而,在超时之前,打印“未完成?!!!”的下一行立即执行。如果调用 currentThread()->Sleep(some_enough_time) 而不是调用 video_thread->wait,那么 video_thread_ 将有时间执行下一个事件并退出。

你可以阅读 QThread 的 Qt 文档,wait 和 terminate 一起用来同步终止一个线程。

=============================== 新代码============== ===================

当你这样做时:

myObj->connect(myThread, SIGNAL(started()), SLOT(dowork()));

信号源是myThread,slot也属于myThread,因为myThread对象是在主线程中创建的,所以作为对象存在于主线程中。你可以调用 myThread->thread() 来查看,它会返回主线程而不是新线程。

但是,started 信号是从新线程即myThread 所代表的线程发出的,因此连接是一个Qt::QueuedConnection。 dowork() 发布在主线程的事件队列中,只有在执行主线程事件循环的 a.exec() 之后才会执行。

其他 2 个 connect 调用也发生同样的事情,所有的槽都将在主线程的 eventloop 中执行。

首先在调用 myThread->start 时从新线程发出 start,dowork 被发布到主线程的事件队列中。

在你调用 a.exec(); 之前什么都没有发生。 所以程序会继续执行cin,然后将stop设置为true,然后打印“Thread is still running?!!”。

Second 当a.exec()被调用时,dowork在主线程中执行并打印“start work”。什么都没做,因为停止标志已经为真,并且从主线程发出完成信号,打印“完成工作”;

第三步 发出finishwork的最后一步,直接调用quit slot。然而,在新线程真正退出之前,主线程已经完成了事件队列,因为没有更多的事件被发布到主线程。应用程序退出,无需等待 quit() 生效。

要测试这是真的,不要修改任何代码,只需在之后添加

emit finishWork();
currentThread()->sleep(1000);

您将看到“线程已完成!”打印出来,因为这让新线程有时间发出 finished(),并且 onfinishThread() 将被添加到主线程的事件队列中。

顺便说一句,您使用线程的方式看起来像 java 风格,这不是标准的 qt 方式。在处理 qt 线程之前,您可以阅读 this

【讨论】:

  • 感谢您的回复。我同意 sleep() 部分,我只是认为 video_thread_->wait() 实际上是“冻结”了 video_thread_。但是,在我使用 this->thread()->msleep(10); 之后(或 this->thread()->currentThread()->msleep(10)),在我设置了 stop_flag 之后,它仍然给了我同样的东西。如果我不将quit() 与stopProcess() 连接,它将永远不会打印“线程已完成”。所以我想我的问题是如何真正“退出”一个线程,或者 Qthred::quit() 如何以及何时工作。谢谢
  • 请检查我的答案的修改,我认为你应该在继续之前阅读一些 qt 线程标准。
  • 很抱歉我错过了重要的一行并且已经在我的代码中更新了,即 myObj->moveToThread(myThread);我在这两种情况下都尝试了您的建议,但仍然无法正常工作。但是我认为您对事件队列的想法是正确的,我会研究一下是否可以解决问题。关于我的线程编程,我看到了这个关于“正确”使用 QThread 的论点,其中一个例子可以在这里找到mayaposch.wordpress.com/2011/11/01/…
【解决方案2】:

这不是日程安排问题。

您在代码中所做的如下所示:

  1. 创建一个线程。
  2. 然后这个线程发出它开始的信号,运行 slot dowork()
  3. 开始一个线程。
  4. 等待用户输入
  5. 关于线程的回显正在运行
  6. 执行事件循环

在第 3 点,线程已经在运行并发出信号。因为 myObj 是在主线程中创建的,并且没有移动到任何其他线程(在那里处理事件),所以您的线程现在不做任何其他事情,而只是旋转事件循环。同时,告诉您要运行 myObj 的 dowork() 的事件会发布在 主线程 上。最后到了第 6 步,你开始执行它的事件循环,它首先找到的是需要在 myObj 上调用 dowork() 的事件。

为了让您清楚 Qt 线程和信号槽连接的工作原理,我建议您阅读this article on Qt blog

简单地修复它,您可以将对象 myObj 移动到您不想运行的线程。

但要真正做到正确,我敢打赌,您真正想要的是继承 QRunnable 并(重新)实现它的 run 方法来执行您在 QThread 中不想做的事情,以便线程它的工作,而不是正确完成,所以你可以加入它。或者根据您的目标,使用QtConcurrent::run(aFunction) 可能会更好

【讨论】:

  • 最佳实践当然是不要继承它。我编辑了我的答案以不提及它,但无论如何,如果你确定你正在这样做,你仍然可以这样做。