Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Worker thread stops processing events during infinite loop in Qt

I've created a Worker object in Qt to process video input indefinitely, and then move it into a QThread to keep the UI thread going. The problem is, I designed it such that the video capture function runs in an infinite loop until interrupted by a flag, and the flag was supposed to be set by a slot in the Worker object, but since the Worker object is inside the infinite loop, it never processes this "quit" slot (or at least I think that's what's happening). I depend on an external library so substituting the video polling for another method is not really an option. Can someone please confirm that this is indeed the problem and suggest a solution? Here's the code:

class worker : public QObject{
    Q_OBJECT
public:
    worker(QObject* parent = NULL);
    ~worker(){}
    Q_SLOT void process();
    Q_SLOT void stop();
private:
    bool quit;
};

worker::worker(QObject *parent) : QObject(parent){
    quit = false;
}
void worker::process(){
    while(!quit){
        //this library call puts the thread to sleep until a frame is available
        WaitForVideoFrame();
    }
}
void worker::stop(){
    quit = true;
}

and then from the UI object I have:

MyWorker = new worker();
QThread* thread = new QThread;
MyWorker->moveToThread(thread);
connect(thread, SIGNAL(started()), MyWorker, SLOT(process()));
QPushButton* stop_button = new QPushButton(this);
connect(stop_button, SIGNAL(clicked(bool)), MyWorker, SLOT(stop()));
thread->start();

The problem here is that when I press the stop_button, nothing happens, the worker keeps running the loop. Is there maybe a function that I can call to yield processing time to the event loop from within the infinite loop? Or a better design/solution for this? Any suggestions are welcome.

like image 859
Armando Martins Avatar asked Oct 25 '25 00:10

Armando Martins


2 Answers

Use the event dispatcher

First, here's the documentation on QAbstractEventDispatcher, which makes queued signals and inter-thread event propagation work.

thread()->eventDispatcher()->processEvents();

Add the line above somewhere in your loop so that it will be called at suitable intervals. It retrieves the current thread, then the thread's event dispatcher, and finally forces the dispatcher to process any pending events (which includes incoming queued signals).

Use a thread-safe method of signalling it's time to stop

Instead of using a thread-unsafe flag, you can call the owning thread's requestInterruption() function.

Subsequently, you can call QThread::isInterruptionRequested(). If it returns true, you can assume it's time to stop doing whatever work you're doing.

Your loop will look something like this in the end:

void worker::process(){
    while(!thread->isInterruptionRequested()){
        thread()->eventDispatcher()->processEvents();
        if (thread()->isInterruptionRequested())
            break;
        WaitForVideoFrame();
    }
}

You have two possibly long-running calls in this loop, the call to process events and WaitForVideoFrame(). I've setup the loop to check if it should quit after both. The standard disclaimer applies: test this for performance in your use case (specifically, checking for an interruption request twice in the loop may be too aggressive).

Should you subclass QThread?

Related from comments: moveToThread vs deriving from QThread in Qt

like image 170
jonspaceharper Avatar answered Oct 27 '25 22:10

jonspaceharper


You are correct with your assumption. Each QThread has it's own QEventLoop and when you emit the signal on different thread it will be queued in target QEventLoop for processing. Once QEventLoop of your worker is processing the event any additional emitted signals will be queued and processed after you are finished with current slot.

However it should not be too hard to fix this; it takes a bit of caution but you can safely derive from QThread and make this long polling work. If you don't need resumable action QThread even has a property that will indicate to you that the interruption has been requested - check QThread::isInterruptionRequested(), e.g.:

class mythread : public QThread {
    Q_OBJECT

public:
    using QThread::QThread;

    void run() override { // Main loop
        while (!isInterruptionRequested()) {
           //this library call puts the thread to sleep until a frame is available
           WaitForVideoFrame();
        }
    }
}

However you do need to be bit careful about proper shutdown sequence (presumably in your MainWindow destructor):

void stopThread() {
    if (m_thread != nullptr) {
        m_thread->requestInterruption();
        m_thread->exit();
        m_thread->wait();
    }
}

EDIT: Sample above works with assumption that you don't need fully working signals & slots; if you do you can update run() method to process it while you are waiting:

class mythread : public QThread {
    Q_OBJECT

public:
    using QThread::QThread;

signals:
    void alive();

public slots:
    void onTimeout() { qDebug() << "timeout!"; }

    void run() override {  // Main loop
        QTimer timer;
        timer.setInterval(1500);
        QObject::connect(&timer, &QTimer::timeout, this, &mythread::alive);
        timer.start();

        while (!isInterruptionRequested()) {
            //this library call puts the thread to sleep until a frame is available
            WaitForVideoFrame();

            // Process event queue
            eventDispatcher()->processEvents(QEventLoop::AllEvents);
        }
    }
};

Then you can hook it up in usual manner:

mythread t;
t.start();

QTimer timer;
timer.setInterval(1000);

QObject::connect(&t, &mythread::alive, [] {
    qDebug() << "I'm alive";
});

QObject::connect(&timer, &QTimer::timeout, &t, &mythread::onTimeout);

timer.start();
like image 41
shrpq Avatar answered Oct 27 '25 22:10

shrpq



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!