Sunday 3 September 2017

Wrong way to use QThread

  There are two ways to use QThread, first solution is inherit QThread and override run function, the other solution is create a controller. Today I would like to talk about second solution and show you how to misused QThread(general gotcha of QThread).

  The most common error I see is calling the function of the worker directly, please do not do that, because in this way, your worker will not work on another thread but the thread you are calling it.

  Allow me prove this to you by a small example. I do not separate implementation and declaration in this post because this make the post easier to read.

case 1 : Call by function


1 : let us create a very simple, naive worker, this worker must be an QObject, because we need to move the worker into QThread.

class naive_worker : public QObject
{
    Q_OBJECT
public:
    explicit naive_worker(QObject *obj = nullptr);

    void print_working_thread()
    {
        qDebug()<<QThread::currentThread();
    }
};

2 : create a dead simple gui by QtDesigner. Button "Call by normal function" will call the function "print_working_thread", directly, button "Call by signal and slot" will call the "print_working_thread" by signal and slot, "Print current thread address" will print the address of main thread(gui thread).


3 : Create a controller


class naive_controller : public QObject
{
    Q_OBJECT
public:
    explicit naive_controller(QObject *parent = nullptr):
    QObject(parent),
    worker_(new naive_worker)
    {
        //move your worker to thread, so Qt know how to handle it
        //your worker should not have a parent before calling
        //moveToThread
        worker_->moveToThread(&thread_);

        connect(&thread_, &QThread::finished, worker_, &QObject::deleteLater);

        //this connection is very important, in order to make worker work on the thread
        //we move to, we have to call it by the mechanism of signal and slot
        connect(this, &naive_controller::print_working_thread_by_signal_and_slot,
                worker_, &naive_worker::print_working_thread);
        thread_.start();
    }

    ~naive_controller()
    {
        thread_.wait();
        thread_.quit();
    }

    void print_working_thread_by_normal_call()
    {
        worker_->print_working_thread();
    }

signals:
    void print_working_thread_by_signal_and_slot();

private:
    QThread thread_;
    naive_worker *worker_;
};

4 : Call it by two different functions and compare their address.


class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButtonPrintCurThread_clicked()
    {
        //this function will be called when 
        //"Print current thread address" is clicked
        qDebug()<<QThread::currentThread();
    }
    void on_pushButtonCallNormalFunc_clicked()
    {
        //this function will be called when
        //"Call by normal function" is clicked
        controller_->print_working_thread_by_normal_call();
    }

    void on_pushButtonCallSignalAndSlot_clicked()
    {
       //this function will be called when
       //"Call by signal and slot" is clicked
       controller_->print_working_thread_by_signal_and_slot();
    }

private:
    naive_controller *controller_;
    naive_worker *worker_;
    Ui::MainWindow *ui;
};

5. Run the app and click the button with following order. "print_working_thread"->"Call by normal function"->"Call by signal and slot" and see what happen. Following are my results

QThread(0x1bd25796020) //call "print_working_thread"
QThread(0x1bd25796020) //call "Call by normal function"
QThread(0x1bd2578bf70) //call "Call by signal and slot"

    Apparently, to make our worker run in the QThread we moved to, we have to call it through signal and slot machanism, else it will execute in the same thread. You may ask, this is too complicated, do we have an easier way to spawn a thread? Yes we do, you can try QtConcurrent::run and std::async, they are easier to use compare with QThread(it is a regret that c++17 fail to include future.then) , I use QThread when I need more power, like thread communication, queue operation.

Source codes

    Located at github.