0

I'm trying to understand what is and isn't allowed when it comes to QWidget and Qt concurrency. I've created a Widget which has a slow_function and I'm considering three cases:

  1. Run the slow_function on the GUI thread. This results in the expected behaviour; the GUI becomes unresponsive while waiting for the function to return.
  2. Use QtConcurrent::run(this, &Widget::slow_function). I was surprised to see that this didn't block the GUI. I've confirmed that the thread affinity of my instance is still the GUI thread, nevertheless, the function seems to be executing on a separate thread. Is such an approach allowed and is this the expected behaviour (documentation link would be really helpful)? Is such an approach safe if I can guarantee that slow_function is thread-safe?
  3. Create a subclass of QThread which holds a pointer to my widget. Override the run method to call slow_function. The behaviour is the same as Case 2. This is also surprising as the thread affinity is still the GUI thread (besides, we are not even allowed to use moveToThread on a QWidget). Why is this running on a separate thread? Is moveToThread meant to be useful only when we are interested in calling slots via signals sent from another thread?

Thank you for reading. Here is the relevant code starting with my the header file:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>
#include <QPushButton>
#include <QLayout>
#include <windows.h>
#include <QtConcurrent/QtConcurrent>
#include <QThread>
#include <QApplication>

class Widget;

class Thread: public QThread
{
public:
    Thread(Widget* widget)
        : m_widget(widget){}

protected:
    void run() override;

private:
    Widget* m_widget;
};

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget* parent = nullptr)
        : QWidget(parent)
    , m_thread(this){

        auto layout = new QVBoxLayout(this);

        auto button = new QPushButton("Case 1: Run on gui thread");
        auto button2 = new QPushButton("Case 2: Run with qtconcurrent");
        auto button3 = new QPushButton("Case 3: Run with qthread");

        connect(button, &QPushButton::clicked, this, &Widget::slow_function);
        connect(button2, &QPushButton::clicked, this, &Widget::use_concurrent);
        connect(button3, &QPushButton::clicked, this, &Widget::use_qthread);

        layout->addWidget(button);
        layout->addWidget(button2);
        layout->addWidget(button3);
    }

    ~Widget()
    {
        m_thread.quit();
        m_thread.wait();
    }

public slots:

    void slow_function()
    {
        qDebug() << "Starting";
        auto gui_thread = QApplication::instance()->thread();
        auto this_thread = thread();
        qDebug() << "Thread affinity is" << (gui_thread == this_thread ? "gui_thread" : "non_gui_thread");
        Sleep(5000);
        qDebug() << "Finished";
    }

    void use_concurrent()
    {
        QtConcurrent::run(this, &Widget::slow_function);
    }

    void use_qthread()
    {
        m_thread.start();
    }

private:
    Thread m_thread;
};



#endif // WIDGET_H

and the main.cpp file:

#include "widget.h"

#include <QApplication>

void Thread::run()
{
    m_widget->slow_function();
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

my_gui

linuxfever
  • 3,763
  • 2
  • 19
  • 43

2 Answers2

0

You should not do any UI things in nonmain threads. The UI means

  • widget interaction
  • model

Run the slow_function on the GUI thread

That's not allowed, nothing should block the GUI thread.

Is such an approach safe if I can guarantee that slow_function is thread-safe?

It's okay to run slow function in a different thread. But...

  • what happens when the user closes the application, but the function still executes?

I have done this, but I prefer to encapsulate it in a separate class.

Create a subclass of QThread which holds a pointer to my widget.

You should only subclass QThread if the subclass is a thread. Like, subclassing an animal class will give you an animal, not a chair with four legs.

Is moveToThread meant to be useful only when we are interested in calling slots via signals sent from another thread?

moveToThread changes the affinity of the object. So, slots are always executed in the correct thread, when you call invokeMethod, the method is executed in the correct thread. When an event is delivered, the event handler is always called in the appropriate thread.

asitdhal
  • 621
  • 2
  • 7
  • 15
  • Thank you for your answer. Your two statements, "You should not do any UI things in nonmain threads" and "It's okay to run slow function in a different thread" seem to contradict each other. In particular, Case 2 is calling a function from a `QWidget` subclass (`slow_function`) in a nonmain thread. Could you elaborate please? – linuxfever Jun 27 '21 at 09:10
  • Slow functions mean functions that do the long computation, like fetching remote data. Do you know any UI thing that takes so long? UI things mean setting data in a QLineEdit or updating a model. – asitdhal Jun 27 '21 at 17:21
  • I'm probably not communicating my question well. I thought that the Qtconcurrent:.run above will take my qwidget and run it in a separate nongui thread. You claim this is allowed but you also claim that qwidget should not be used in a nongui thread. Doesn't the latter contradict the former? – linuxfever Jun 27 '21 at 17:37
0

The whole purpose of QtConcurrent::run() is to make running heavy workloads in a thread easier. If your use-case allows for it, great! Pair it with a QFutureWatcher to retrieve the result after your slow_function is finished.

Using QThread is another option, but it makes more sense when you have a long-lived object. Instead of subclassing, I find it easier to use the worker model: create a worker class with signals/slots, call moveToThread on it with a vanilla QThread object, connect/subscribe, start the thread

QWidget is a way to create GUI functionality in Qt. While it may be possible to use such objects in non-gui threads, it's best you separate your compute workloads from GUI (don't put your slow_function into widget classes).

d.Candela
  • 154
  • 1
  • 5
  • 8