Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

:Qt Multithreading Patterns: Cross-Thread Signal-Slot Communication

Tech May 10 2

Threads enable concurrent execution paths within a program. In C++, each thread begins its lifecycle through an entry function—main() serves as the primary thread's entry point. When simultaneous operations are required, spawning additional threads becomes essential. The C++11 standard introduced native threading support:

std::thread worker(doWork);
continueOtherTasks();

This pattern proves particularly valuable in GUI applications. The main event loop handles user interactions, while background threads manage computasions, I/O, or network operations. Without this separation, lengthy operations would freeze the interface.

Qt provides two primary mechanisms for multithreading:

Subclassing QThread: Override the run() method. Only code within run() executes in the new thread. Other member functions and slots remain in the thread where the object was created.

Object Movement: Instantiate QThread and relocate QObject instances using moveToThread(). Since QThread::run() activates an event loop by default, all slots of the moved object automatically process in the new thread.

The following demonstration employs the second approach. It creates a responsive interface with three buttons while performing work in a separate thread.

The TaskProcessor class encapsulates background operations. It defines two slots: processLightTask() for quick operations and processHeavyTask() for simulated long-running work. Both output thread identifiers to verify execution context.

#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QVBoxLayout>
#include <QThread>
#include <QObject>
#include <iostream>
#include <thread>

// Worker object that processes tasks in a separate thread
class TaskProcessor : public QObject {
    Q_OBJECT
public:
    TaskProcessor() = default;

signals:
    void progressUpdated(int value);

public slots:
    void processLightTask() {
        std::cout << "Light task executed in thread: " 
                  << std::this_thread::get_id() << "\n";
        auxiliaryMethod();
    }
    
    void processHeavyTask() {
        std::cout << "Heavy task starting in thread: " 
                  << std::this_thread::get_id() << "\n";
        QThread::sleep(5);
        std::cout << "Heavy task finished\n\n";
    }

private:
    void auxiliaryMethod() {
        std::cout << "Auxiliary method running in thread: " 
                  << std::this_thread::get_id() << "\n\n";
    }
};

The ControlPanel class constructs the user interface with three buttons. The onHeavyButtonClicked() method emits a signal that triggers background processing, then immediately prints the GUI thread ID to demonstrate non-blocking behavior.

// Main window running in the GUI thread
class ControlPanel : public QMainWindow {
    Q_OBJECT
public:
    ControlPanel(QWidget *parent = nullptr) : QMainWindow(parent) {
        taskBtn = new QPushButton("Execute Light Task");
        heavyBtn = new QPushButton("Execute Heavy Task");
        quitBtn = new QPushButton("Quit");
        
        mainLayout = new QVBoxLayout;
        mainLayout->addWidget(taskBtn);
        mainLayout->addWidget(heavyBtn);
        mainLayout->addWidget(quitBtn);
        
        centralWidget = new QWidget;
        centralWidget->setLayout(mainLayout);
        setCentralWidget(centralWidget);
    }
    
    ~ControlPanel() {
        delete centralWidget;
    }

signals:
    void triggerLightTask();
    void triggerHeavyTask();

public slots:
    void onHeavyButtonClicked() {
        emit triggerHeavyTask();
        std::cout << "Heavy task signal emitted from GUI thread: " 
                  << std::this_thread::get_id() << "\n\n";
    }

private:
    QPushButton *taskBtn;
    QPushButton *heavyBtn;
    QPushButton *quitBtn;
    QWidget *centralWidget;
    QVBoxLayout *mainLayout;
};

The MultiThreadedApp class orchestrates the entire system. It instantiates both the interface and worker objects, establishes cross-thread connections using Qt::QueuedConnection, and relocates the processor to the background thread. The thread starts before showing the window.

// Custom application class
class MultiThreadedApp : public QApplication {
    Q_OBJECT
public:
    MultiThreadedApp(int &argc, char **argv) : QApplication(argc, argv) {
        std::cout << "Application initialized in thread: " 
                  << std::this_thread::get_id() << "\n\n";
        
        window = new ControlPanel;
        processor = new TaskProcessor;
        workerThread = new QThread;
        
        // Connect signals to slots across threads
        connect(window->taskBtn, &QPushButton::clicked,
                processor, &TaskProcessor::processLightTask, Qt::QueuedConnection);
        connect(window->heavyBtn, &QPushButton::clicked,
                window, &ControlPanel::onHeavyButtonClicked);
        connect(window, &ControlPanel::triggerHeavyTask,
                processor, &TaskProcessor::processHeavyTask, Qt::QueuedConnection);
        connect(window->quitBtn, &QPushButton::clicked,
                this, &QApplication::quit);
        
        // Move processor to worker thread and start
        processor->moveToThread(workerThread);
        workerThread->start();
        window->show();
    }
    
    ~MultiThreadedApp() {
        workerThread->quit();
        workerThread->wait();
        delete window;
        delete processor;
        delete workerThread;
    }

private:
    ControlPanel *window;
    TaskProcessor *processor;
    QThread *workerThread;
};

int main(int argc, char *argv[]) {
    MultiThreadedApp app(argc, argv);
    return app.exec();
}

When executed, clicking "Execute Light Task" immediately processes in the background thread. Pressing "Execute Heavy Task" triggers a five-second sleep without blocking the interface—the GUI remains responsive throughout. Thread IDs printed to the console confirm that slots execute in the worker thread while signals originate from the main thread.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.