Qt 메인 쓰레드 이벤트 처리

Qt 환경 프로그래밍 중 어떠한 동작에 따라 이벤트가 발생할 경우 해당 이벤트에 대한 루프 처리 시,

메인 쓰레드에서 발생한 동작 중 무슨 이벤트가 우선으로 수행되는지에 대한 의문이 들 때가 있습니다.

 

다음과 같이 멀티 쓰레드 프로그램에서 여러 개의 쓰레드가 실행 중 각 쓰레드 별로 메인 쓰레드로 Signal을 발생시킬 경우

메인 쓰레드에서의 동작, 메인 쓰레드에서 반복문을 돌고 있던 도중에 다른 이벤트가 발생할 때의 우선 순위나 백그라운드로 GUI에서 표시되고 있는 영상 등의 동작을 예로 들 수 있는데, 이는 Qt가 제공하는 Slot / Signal 및 GUI 적인 이벤트가 많기 때문입니다.

멀티 쓰레드 프로그램의 경우 Signal을 내보낸 순서대로 메인 쓰레드에서 Slot을 절차적으로 실행합니다. 

따라서 멀티 쓰레드 내에서 메인 쓰레드로 Signal을 방출할 경우 방출한 쓰레드의 수가 많아 질 수록 메인 쓰레드에 진입하기 위한 대기 시간이 길어집니다. 

 

위 상황을 예로 들면, 아래와 같이 20개의 멀티 쓰레드에서 메인 쓰레드로 Signal을 방출하여 메인 쓰레드에 있는 Slot에 연결하는 예시입니다.

멀티 쓰레드에서 각각 Signal을 보낼 경우, 메인 쓰레드에서 Slot 수행은 쓰레드 별로 들어온 순서에 따라 하나씩 수행합니다.

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    for(int thr = 0; thr < 20; thr++)
    {
        testthread[thr] = new TestThread(this); // Make Multi Thread
        connect(testthread[thr], SIGNAL(ThreadEnd(int)), this, SLOT(PrintToScreen(int))); // Multi Thread and Main GUI Connect to Signal/Slot
        testthread[thr]->start();
    }
}

 

Slot에서 이벤트 처리 시에 해당 Slot 내에 반복문이나 타이밍을 독점하는 함수를 사용할 경우에는 정도에 따라 프로그램에 영향을 미칩니다. 

메인 쓰레드에서 반복문이 길어 질 경우 GUI에서 실행 중인 영상에 버퍼링이 생기거나, 발생한 이벤트가 동작하지 않는 경우도 볼 수 있습니다.
따라서 프로그램 런타임 간 장기적으로 사용해야 하는 기능이나 함수들은 메인 쓰레드에서 분리하는 것이 원칙입니다.

QCoreApplication / QGuiApplication / QApplication 클래스 별 정의 및 특징

main.cpp에서는 프로그램 런타임 시 발생하는 이벤트를 처리하기 위해

QCoreApplication, QGuiApplication, QApplication 이 세 가지의 Class 중 하나를 생성하여야 합니다.

각 Class 별 정의와 특징을 살펴보겠습니다.

- QCoreApplication: Base class. Use it in command line applications.

QCoreApplication은 이벤트 루프에서는 내부 이벤트 및 운영 체제의 이벤트 (타이머 및 소켓 등)를 처리하는데 exec() 호출로 시작됩니다.

이벤트 루프가 종료 (quit()) 될 때까지 exec() 함수는 반환되지 않으며 일반적으로 exec() 함수는 main() 함수의 return으로 호출합니다.

- QGuiApplication: Base class + GUI capabilities. Use it in QML applications.

QGuiApplication은 QCoreApplication의 파생클래스로써 윈도우 시스템과 관련된 이벤트(입력장치 이벤트 등) 처리가 포함되어 있습니다.

또한 폰트(QFont)를 설정하거나 QClipboard, QInputMethod, QWindowList 등을 제공합니다.

GUI가 없는 Qt 어플리케이션의 경우 Qt GUI 모듈에 의존하지 않으므로 QCoreApplication을 대신 사용합니다.

- QApplication: Base class + GUI + Support for widgets. Use it in QtWidgets applications.

QApplication 은 QWidget 기반 애플리케이션에 필요한 기능을 갖춘 QGuiApplication의 파생클래스입니다.

위젯의 초기화, 마무리등 QWidget과 밀접하게 관련된 일을 수행하기 때문에 QWidget을 사용하는 응응프로그램에서 QApplication을 생성해야 합니다.

결론적으로 이벤트 루프는 응용 프로그램의 백그라운드에서 동작하고 운영 체제에서 들어오는 이벤트 (마우스 이동, 클릭, 페인트 이벤트, 하드웨어 이벤트 등)와 내부 통신(Signal/Slot)을 처리하는 무한 루프입니다. maic.cpp에서 app.exec() 호출 시 이벤트 루프가 시작됩니다.

 

소스 및 결과 화면

이번 예제는 메인 쓰레드에서 무한 루프를 돌고 있을 경우 QProcess 및 QTimer의 Signal / Slot 이벤트 발생 시에 프로그램 동작을 보겠습니다.
아래의 예제에서 92번 째 줄의 QApplication::processEvents() 함수를 호출하지 않으면 프로그램은 정상 동작하지 않습니다.


무한 루프에서 작업을 계속에서 수행하고 있기때문에 다른 이벤트들에 대해 처리하지 못하는 상태로 프로그램은 응답성을 잃었고, 

이 경우 QApplication::processEvents() 함수를 호출하여 응용 프로그램의 응답성을 유지할 수 있습니다.

하지만 QApplication::processEvents() 함수를 호출하는 순간에 많은 이벤트가 들어오게 되면 오동작의 원인이 되고,

전체 프로그램의 구성상 아래와 같은 코드는 좋지 않은 디자인이기 때문에 메인 쓰레드에서 무한 루프를 없애거나 다른 쓰레드로 이동시켜야 합나다.

 

Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QMainWindow>
#include <QTimer>
#include <QProcess>
 
namespace Ui {
class MainWindow;
}
 
class MainWindow : public QMainWindow
{
    Q_OBJECT
 
public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();
    void test_function();
 
private:
    Ui::MainWindow *ui;
    QProcess * process;
    QTimer *timer;
 
private slots:
    void process_output();
    void timeout_slot();
    void on_pushButton_clicked();
};
 
#endif // MAINWINDOW_H
 
 
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
 
    // QProcess Setting
    process = new QProcess(this);
    connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(process_output()));
 
    // QTimer Setting
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(timeout_slot()));
}
 
MainWindow::~MainWindow()
{
    delete ui;
}
 
void MainWindow::timeout_slot()
{
    // QTimer Slot
    ui->t_textBrowser->insertPlainText("Time Out: 1 sec\n");
}
 
void MainWindow::process_output()
{
    // QProcess Slot
    QByteArray readdata;
    readdata = process->readAllStandardOutput();
    ui->p_textBrowser->insertPlainText(readdata);
    readdata.clear();
}
 
void MainWindow::test_function()
{
    // External Program Start
    QString program = QString("/home/build-Message_Program-Desktop_Qt_5_8_0_GCC_64bit-Debug/Message_Program");
    process->start(program);
    process->waitForStarted();
 
    // Timer Start
    timer->start(1000);
 
    // Infinite Loop Test Start
    while(1)
    {
        while(timer->isActive())
        {
            // Event Processing Function
            QApplication::processEvents();
            break;
        }
    }
}
 
void MainWindow::on_pushButton_clicked()
{
    // Test Start Button Click
    test_function();
}

 

Result Image