2
votes

I'm new to Qt, and want to simply display a video in Qt GUI. I basically got everything figured out, except for some details handling the QThread, which is really annoying.

I reformulate my question into a simpler program, hope it will explains better

first I define this QObject class

#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!";
}

then the main function

#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();
}

As you can see, I even used cin to try let the dowork() run first, but it didn't work at all, the output is

output

so I'm really confused on how scheduling works for QThread...

Also, if you uncomment the part

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

the output is exactly the same! only stays a while after printing

The thread is still running?!!!

Would anyone help me with this? Thanks a lot. You may simply copy all the code and test it yourself.


-------------------------Original Question, bad explanation, please ignore....----------------------------------------

I made a videoObj Class with only one function to Query the frames, the function is defined as:

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";
}

Basically above code is just query frames and whenever got one emit the

SIGNAL imgProcessed(qimgOriginal)

and whenever the stop_flag is set on, stop the while loop and emit the

SIGNAL stopProcess()

I use this class in the MainWindow Class, here is how I define the connection in the constructor:

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()));
}

Above code works fine in frame query. The problem I don't understand is, if I set video_obj_->stop_flag on in any MainWiow member function, the ProcessFrame() in videoObj Class should emit stopProcess() signal and trigger quit() of video_thread_, and then the thread should finish, that is video_thread_->finished() is true.

However, if I do something like:

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";
}

It will give me the output:

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

Which means triggering the quit() is not finish the thread, or quit() has not been triggered.

If I use:

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

The program will just freeze.

Is anyone can help me with this, it really confuse me about this quit() and finished()... Thanks!

2
Are you sure you start your thread? I didn't notice you use the QThread::start() function.hank
since "stop thread" is printed, it start should have been called but not pasted here.Min Lin
to hank, yes I did. Sorry I just realize I didn't put the start part of the code ... I updated my postAustin

2 Answers

4
votes

Since when you call stop_flag=1, the video_thread_ finishes the current function which is

ProcessFrame

before ProcessFrame finish, quit is called on the video_thread_ through emit stopProcess().

However, quit is different from terminate, terminate can exit the thread any time (but it is not safe), quit works with the event loop, if the thread has no eventloop, quit has no effect.

I guess before qthread execute the next event in the eventloop, it checks some flag, which can be set by quit, it the flag is set by quit, then it won't execute the next event in the eventloop. Or it can also be that a quit event is inserted into the eventloop, and the next event in the eventloop will be quit.

After stop_flag = 1, you called video_thread_->wait, that will block the video_thread_ from executing the next event in the eventloop, thus the quit will not take effect before time out, however, the nextlines which print "not finished?!!!" is executed immediately. Instead of calling video_thread->wait, if you call currentThread()->Sleep(some_enough_time), then there will be time for the video_thread_ to execute the next event and quit.

You can read the Qt documentation of QThread, wait is used with terminate to terminate a thread synchronously.

============================== The new code ================================

When you do:

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

The signal source is myThread, the slot also belongs to myThread, since the object "myThread" is created in the main thread, thus as a object it lives in the main thread. You can call myThread->thread() to see this, it will return the main thread instead of the new thread.

However, the started signal is emitted from the new thread namely the thread that myThread represents, thus the connection is a Qt::QueuedConnection. dowork() is posted in the event queue of main thread, and it'll be executed only after a.exec() which executes the main threads eventloop.

The same thing happens to the other 2 connect calls, all the slots will be executed in the eventloop of the main thread.

First the start is emitted from the new thread when myThread->start is called, dowork is posted in the main thread's event queue.

Nothing really happens before you call a.exec(); So the program will go ahead to cin and then set stop to true, and then print "Thread is still running?!!".

Second When a.exec() is called, the dowork is executed in the main thread and "start work" is printed. Nothing is done because the stop flag is already true, and the finishwork signal is emitted from the main thread, print "finish do work";

Third The last step the finishwork is emitted, and the quit slot is directly called. However, before the new thread can really quit, the main thread has already finished the eventqueue, because no more events are posted to the main thread. The application exits without waiting for quit() to take effect.

To test this is true, do not modify any of your code, just add after

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

you will see "thread is finished!" printed, because this make time for the new thread to emit finished(), and the onfinishThread() will be add to the main thread's eventqueue.

BTW, your way of working with thread looks like java style, which is not the standard qt way. You can read this before you work on qt thread.

1
votes

This is not a scheduling issue.

That you did in your code looks like:

  1. Create a thread.
  2. Then this thread emits a signal that it started, run slot dowork()
  3. start a thread.
  4. Wait for user input
  5. echo about thread is running
  6. execute event loop

At point 3 thread is already running and signalled about that. Because myObj is created in main thread and not moved to any other thread (to process events there), your thread does not do anything else now, but just spins event loop. At the same time event that tells you want to run myObj's dowork() is posted on the main thread. At last then it comes to step 6 you start to execute event loop of it, and first thing it finds is event that it needs to call dowork() on myObj.

To make it clear to you how Qt threads and signal-slot connection works, I recommend you to read this article on Qt blog.

In simple to fix it, you can move your object myObj to the thread that you wan't to run.

But to make this really correct, I bet that you really want is to subclass QRunnable and (re-)implement it's run method to do the stuff you wan't in QThread, so that thread does it's job, than finished correctly, so you can join on it. Or depending on your goal you might be even better with using QtConcurrent::run(aFunction)