1. 程式人生 > 其它 >Qt主介面卡死的解決方案-一些具體實現方式(轉)

Qt主介面卡死的解決方案-一些具體實現方式(轉)

轉自:(7條訊息) Qt主介面卡死的解決方案-一些具體實現方式_qq_37518975的部落格-CSDN部落格

Qt主介面卡死的解決方案-一些具體實現方式


簡介
我們在寫UI檔案的時候,有很多情況下,是需要介面來處理業務中某些耗時的操作,這時候如果不處理好介面相關的邏輯的話,主介面就會卡死,這時候就需要我們上多執行緒了

邏輯1
首先上業務上一個很簡單的栗子

比如我們的程式碼中有這麼一個耗時的操作

   // 第一種耗時的操作
    auto fWhile1 = [] ()
    {
        for (int i = 0; i < 1000000; i++)
        {
            qDebug()<<i<<endl;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

把這個程式碼繫結到一個按鈕事件上

connect(ui->pushButton1, &QPushButton::clicked, fWhile1);
  • 1

然後點選。發現介面卡死了,很正常,必須得等到這段程式碼耗時完成之後才能繼續操作介面,這段程式碼是太不友好了,不清真,所以我們要改一下。

邏輯2

如何改動,可以看下這個函式

QCoreApplication::processEvents
  • 1

來一起看下官網介紹

Processes all pending events for the calling thread according to the specified flags until there are no more events to process.
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
In the event that you are running a local loop which calls this function continuously, without an event loop, the DeferredDelete events will not be processed. This can affect the behaviour of widgets, e.g. QToolTip, that rely on DeferredDelete events to function properly. An alternative would be to call sendPostedEvents() from within that local loop.
Calling this function processes events only for the calling thread.
Note: This function is thread-safe.
  • 1
  • 2
  • 3
  • 4
  • 5
  • You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
  • 當程式忙於執行長時間操作(例如複製檔案)時,您可以偶爾呼叫此功能。
    我們就暫時就這個(滑稽。
    接下來可以把程式碼搞成這種了。
 auto fWhile2 = [] ()
    {
        for (int i = 0; i < 1000000; i++)
        {
            qDebug()<<i<<endl;
            QApplication::processEvents();
        }
    };
    connect(ui->pushButton2, &QPushButton::clicked, fWhile2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

這種程式碼在配置不好的機器上實際上還是有點小問題,比如我的小破本子。還是會有點卡的。我覺得使用者一般是可以接受這種情況的。

邏輯3
實際上這個邏輯還有一個問題,就是如果我的業務程式碼不是迴圈該怎麼辦呢,這時候我們可以用新的類介面

QtConcurrent::run
  • 1

這個類。這個類是可以將一個函式放到新的執行緒裡來執行。再加上

QFuture<T>
  • 1

這個類,可以控制這個新的執行緒函式開始,控制,結束。
具體可以檢視官方文件,我這裡就上個簡單的栗子

//耗時的操作
static bool function_needmoretime()
{
    for (int i = 0; i < 1000000; i++)
    {
        qDebug()<<i<<endl;
    }
    return true;
}

    // three
    auto fWhile3 = [] () -> void
    {
        QFuture<bool> future = QtConcurrent::run(function_needmoretime);
        while(!future.isFinished())
        {
            QApplication::processEvents(QEventLoop::AllEvents, 100);
        }
    };
    connect(ui->pushButton3, &QPushButton::clicked, fWhile3);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

QFuture + QtConcurrent這個框架非常強大,可以將執行緒同步非同步狀態抽象出來,讓程式設計師不用太關心這些。這只是一個最簡單的栗子。我的小破本子來執行這個是一點都不卡的。介面依舊如絲滑般流暢。

邏輯4-執行緒
執行緒基礎那種廢話我就不多說了。道理大家都懂,我直接上wiki。

執行緒(英語:thread)是作業系統能夠進行運算排程的最小單位。它被包含在程序之中,是程序中的實際運作單位。一條執行緒指的是程序中一個單一順序的控制流,一個程序中可以併發多個執行緒,每條執行緒並行執行不同的任務。在Unix System V及SunOS中也被稱為輕量程序(lightweight processes),但輕量程序更多指核心執行緒(kernel thread),而把使用者執行緒(user thread)稱為執行緒。
執行緒是獨立排程和分派的基本單位。執行緒可以為作業系統核心排程的核心執行緒,如Win32執行緒;由使用者程序自行排程的使用者執行緒,如Linux平臺的POSIX Thread;或者由核心與使用者程序,如Windows 7的執行緒,進行混合排程。
同一程序中的多條執行緒將共享該程序中的全部系統資源,如虛擬地址空間,檔案描述符和訊號處理等等。但同一程序中的多個執行緒有各自的呼叫棧(call stack),自己的暫存器環境(register context),自己的執行緒本地儲存(thread-local storage)。
一個程序可以有很多執行緒,每條執行緒並行執行不同的任務。
在多核或多CPU,或支援Hyper-threading的CPU上使用多執行緒程式設計的好處是顯而易見,即提高了程式的執行吞吐率。在單CPU單核的計算機上,使用多執行緒技術,也可以把程序中負責I/O處理、人機互動而常被阻塞的部分與密集計算的部分分開來執行,編寫專門的workhorse執行緒執行密集計算,從而提高了程式的執行效率。
  • 1
  • 2
  • 3
  • 4
  • 5

執行緒的建立有兩種方式,第一種是繼承QThread的方式,然後重寫run,但是這種方式官方已經不推薦了。官方不推薦的我們就不要這樣寫了,我們這裡討論的是第二種方式。

繼承QObject ,move到新的執行緒中。

重寫 QObject

// 標頭檔案
class workThread : public QObject
{
    Q_OBJECT
public:
    workThread(QObject* parent = nullptr);
    ~workThread();
public slots:
    void start1();
    void doWork();
signals:
    void workFinished();
    void workStart();
};

//cpp
workThread::workThread(QObject* parent) : QObject (parent)
{
}
workThread::~workThread()
{
}
void workThread::start1()
{
    emit workStart();
    doWork();
}
void workThread::doWork()
{
    for (int i = 0; i < 1000000; i++)
    {
        qDebug()<<i<<endl;
    }
    emit workFinished();
}
  • 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

使用方法

 QThread* m_workerThread = new QThread();
    workThread* worker = new workThread();
    worker->moveToThread(m_workerThread);

    connect(m_workerThread, &QThread::started, worker, &workThread::start1);
    connect(worker, &workThread::workFinished, m_workerThread, &QThread::quit);
    connect(m_workerThread, &QThread::finished, m_workerThread, &QThread::deleteLater);

    //也可以退出釋放資源
//    connect(qApp, &QApplication::aboutToQuit, worker, &QObject::deleteLater);
//    connect(worker, &QObject::destroyed, m_workerThread, &QThread::quit);
//    connect(m_workerThread, &QThread::finished, m_workerThread, &QThread::deleteLater);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

總結下這樣的操作介面是一點都不卡的,因為延遲的操作我們放到新的執行緒中了。
如果需要傳遞資料的話,可以將資料通過訊號槽的方式傳遞。

之所以官方不推薦重寫QThread也是因為無法使用訊號槽
想繼承QThread的話也可以,這個繼承QThread的類也需要moveToThread,這種做法不清真,所以不希望大家用。

邏輯5 執行緒 + 定時器

實際上,就是邏輯4的進階版本,再加個定時器,每隔兩秒輸出當前時間

class TimerThread : public QObject
{
    Q_OBJECT
public:
    TimerThread(QObject* parent = nullptr);
    ~TimerThread();
public:
    void run();
    void doWork();
signals:
    void workStart();
    void workFinished();
};

static int timerCount = 0;
TimerThread::TimerThread(QObject* parent): QObject (parent)
{
}
TimerThread::~TimerThread()
{
}
void TimerThread::run()
{
    emit workStart();
    QTimer *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &TimerThread::doWork);
    timer->start(2000);
}
void TimerThread::doWork()
{
    timerCount ++;
    if (timerCount > 100)
        emit workFinished();
    qDebug()<<QTime::currentTime()<<endl;
}
  • 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

業務程式碼在這裡

 auto fTimerThreadStart = [=] () -> void
    {
        fiveThread->start();
    };
    connect(ui->threadButton2, &QPushButton::clicked, fTimerThreadStart);
    connect(fiveThread, &QThread::started, timerObject, &TimerThread::run);
    connect(timerObject, &TimerThread::workFinished, fiveThread, &QThread::quit);
    connect(fiveThread, &QThread::finished, fiveThread, &QThread::deleteLater);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

介面也是灰常絲滑般流暢的。具體的業務邏輯需求可以再想。