Simplify QThread and UI synchronization with lambda

Posted by amesowe on Sat, 13 Nov 2021 04:22:40 +0100

Used to C# and JAVA, I found it inconvenient to use QT again. We can't say who is better. At least some places can learn from and complement each other, such as the use of threads.

There are threads and tasks in C# to create background Task threads. There are threads and Runnable in JAVA, but it is relatively troublesome in QT, or at least the code logic is not so smooth. But it may also be the sense of non violation caused by personal habits or poor Kung Fu. Anyway, let's try to write something similar to C# on QT.

Threads are mostly used for background tasks and UI synchronization. To write a way similar to C # on QT, first find out how C # realizes multithreading and UI synchronization.

1. Thread and UI synchronization in C #

In c#, you can create a thread through new Thread (Action), and then call the Start method to start the thread. The action used to create a thread is a delegate, which can be considered as a function pointer in c + +, and a function pointer without parameters and return results.
After the thread is created, you can update the UI in the Action through the Invoke(Action) of the UI object.

In C #, the Action can be written in lambda, so it is mostly used in code writing. See the following code for details.

			//Create thread
            var thread = new Thread(() => {
                //Thread synchronization
                this.Invoke(new Action(() => {
                    this.Text = "Use in child threads Invoke to update UI window";
                }));
            });
            //Start thread
            thread.Start();

The UI synchronization mechanism in c # is message, including MFC in c + +, winform in c # and WPF. Looper in Android is also similar.

The simple understanding of this mechanism is that the UI thread is a loop body, constantly fetching messages from a message queue and executing the message. The expression of messages is diversified, regular and systematic. For example, clicking the mouse, pressing the keyboard and so on can be expressed by messages.

Hand in a picture to illustrate the message mechanism

2. How to realize multithreading and UI synchronization in QT

There are two common ways to write multithreading in QT. One is to define a subclass of QThread and implement task processing logic in the subclass. One is to define a QObject subclass, implement task processing logic in the subclass, create a QThread when using, and execute the subclass moveToThread into the QThread.
For details, please refer to https://subingwen.cn/qt/thread/

UI synchronization in QT adopts the mechanism of signal and slot, which is similar to winform in C# and mfc in vc + +. I won't go into detail.

3. How to simplify

After understanding the thread implementation methods of c# and QT, we can find an obvious difference is that lambda is adopted. Lambda is included in the standard only when it is c11, while QT was born long before c11. My personal understanding is that QT has always followed the original mode. So I want to simplify with lambda.

See the following code for details. In order to facilitate the demonstration, the function body is also in the header file,
(here is a tip of QTCreator. Right click the function declaration or class declaration menu refactor - > move definition... You can automatically switch between. h and. cpp for a single function body or all class function bodies.)

Custom function

//Define the function structure, which is similar to C#'s Action
typedef std::function<void ()> FUNC;

//Define a custom thread class derived from QThread
class MyThread:public QThread{
    Q_OBJECT
private:
    FUNC m_func=0;//Record the functions executed by the thread
public:
    //Constructor, func parameter is the function executed by the thread
    explicit MyThread(FUNC func=NULL, QObject *parent = Q_NULLPTR):QThread(parent),
        m_func(func)
    {
        //Link signals and slots are the key to the synchronization of sub threads to the UI.
        connect(this,&MyThread::invokeSignal,this,&MyThread::invokeSlot);
    }

    ~MyThread()
    {
        //Disconnect the signal slot
        disconnect(this,&MyThread::invokeSignal,this,&MyThread::invokeSlot);
    }

    //Set the function executed by the thread. Although func already exists in the constructor, in some scenarios, MyThread needs to be accessed in lambda, so this function is required
    //This function needs to be called at start()
    void setFunc(FUNC func){
        this->m_func=func;
    }

    //Synchronize to UI thread call function
    void invoke(FUNC func){
        emit invokeSignal(func);
    }

protected:
    //If the parent function is rewritten, the function will be called when a single thread starts execution, which can be simply regarded as the entry of the thread.
    void run() override{
        //Call the function body to execute the task
        m_func();
    }

signals:
    //UI synchronization signal
    void invokeSignal(FUNC fun);

public slots:
    //The slot function of UI synchronization, which is generally called to synchronize to the UI thread
    void invokeSlot(FUNC fun){
        //Call the UI synchronization function.
        fun();
    }


};


Test code

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{

    ui->setupUi(this);

    //Create a button for testing
    QPushButton * btn=new QPushButton(this);
    btn->setFixedSize(100,30);
    btn->move(100,100);
    btn->show();

    //Register anonymous function types
    qRegisterMetaType<FUNC>("FUNC");

    //Output UI thread ID
    qDebug()<<"UIThread ID:"<<QThread::currentThreadId();

    //Create a new thread
    MyThread * th0=new MyThread();

    //Subthread task logic, print numbers, update UI
    auto func=[=](){
        int i=0;
        while(i++<20){
            qDebug()<<"sub thread id:"<< QThread::currentThreadId() <<"  i:"<<i<<"  tid:"<<QThread::currentThreadId();
            QThread::msleep(500);

            //Synchronize to the UI thread to update the UI. Because th0 is used here, it cannot be defined in the constructor of MyThread
            th0->invoke([=](){
                 btn->setText(QString::number(i));
                 btn->move(i, 100);
            });
        }
    };
    //Set task function
    th0->setFunc(func);
    //Start thread execution
    th0->start();

    //Stop the thread at the end of the form
    connect(this,&MainWindow::destroyed,[=](){
        //End the thread. Sometimes it will not take effect. Explain this problem next time.
        th0->exit(0);
        th0->wait(3000);
    });

}

4. Subsequent improvement
There are several problems in the above implementation, such as the exception when the window is released but the thread is still running, and the UI synchronization is required, and the thread cannot be stopped when the window is closed. Therefore, a mechanism of status update and notification is needed. I'll write about it today. I'll add it next time.

Topics: C++ Qt