在Qt中將函數發送到主線程執行


考慮這樣一種需求,使用Qt的線程類QThread在后台執行操作(比如說拷貝文件)的時候發生了錯誤,產生了一個錯誤信息需要提醒給用戶,在后台輸出很顯然是不夠的,因為用戶可能根據就沒有任何控制台可供程序輸出信息。

 

這是本人自己做得一個仿Win10文件拷貝對話框的一個文件拷貝對話框

該問題糾結到根本是因為Qt的任何窗口代碼都必須在主線程(也就是main函數所在的那個線程)中執行。如果在后台發生錯誤需要出對話框提示給用戶的話,必須能夠將后台信息阻塞性的發送給前台,在前台圖形類的程序執行完畢后再返回。

那么在Qt中前后台如何通信呢,由於QThread是繼承自QObject的,很自然大家會想到使用信號槽來連接:

bool QObject::connect ( const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection )

相信大家對於以上connect函數的定義是非常熟悉了,關鍵在於最后一個參數“Qt::ConnectionType”,以下是該參數的詳細說明:

Qt::DirectConnection
When emitted, the signal is immediately delivered to the slot.
假設當前有4個slot連接到QPushButton::clicked(bool),當按鈕被按下時,QT就把這4個slot按連接的時間順序調用一遍。顯然這種方式不能跨線程(傳遞消息)。

Qt::QueuedConnection
When emitted, the signal is queued until the event loop is able to deliver it to the slot.
假設當前有4個slot連接到QPushButton::clicked(bool),當按鈕被按下時,QT就把這個signal包裝成一個 QEvent,放到消息隊列里。QApplication::exec()或者線程的QThread::exec()會從消息隊列里取消息,然后調用 signal關聯的幾個slot。這種方式既可以在線程內傳遞消息,也可以跨線程傳遞消息。

Qt::BlockingQueuedConnection
Same as QueuedConnection, except that the current thread blocks until the slot has been delivered. This connection type should only be used for receivers in a different thread. Note that misuse of this type can lead to dead locks in your application.
與Qt::QueuedConnection類似,但是會阻塞等到關聯的slot都被執行。這里出現了阻塞這個詞,說明它是專門用來多線程間傳遞消息的。

Qt::AutoConnection
If the signal is emitted from the thread in which the receiving object lives, the slot is invoked directly, as with Qt::DirectConnection; otherwise the signal is queued, as with Qt::QueuedConnection.
這種連接類型根據signal和slot是否在同一個線程里自動選擇Qt::DirectConnection或Qt::QueuedConnection

 

很顯然在不同的線程間發送信號還希望發送信號的一端必須阻塞性的等待槽函數返回,那么“Qt::BlockingQueuedConnection”是我們的不二之選。

連接方式有了,然后是數據共享的問題,試想“后台線程中的數據如何能夠被前台所使用,而且前台后台不一定在一個類里面?把數據打包通過信號傳給前台?”想想就是很麻煩的事情,難道每個這樣的需求場合都要做一遍這樣的事情嗎?感謝時間,因為時間穿過2011年,C++的新標准已經完美的解決了這個問題,那就是函數對象。

Qt的4.8.6版本所使用的mingw4.9.2版本是支持C++11的,如果你用的是老掉牙的rhel5系統,則需要升級編譯器了,因為C++11要在GCC 4.5以上的版本中才會支持。

首先我們定義一個類:FunctionTransfer(函數大挪移),這個類繼承自QObject,並使用Q_OBJECT標簽來使用信號槽機制。代碼中的“std::tr1::function<void()>”就是C++標准庫中大名鼎鼎的函數對象。

class FunctionTransfer : public QObject

{

    Q_OBJECT

public:

    ///@brief 構造函數

    explicit FunctionTransfer(QObject *parent = 0);

public:

    ///@brief 制定函數f在main中執行

static void execinmain(std::tr1::function<void()> f);

signals:

    ///@brief 在別的線程有函數對象傳來

    void comming(std::tr1::function<void()> f);

public slots:

    ///@brief 執行函數對象

    void exec(std::tr1::function<void()> f);

 

};

 

然后是源文件:

 

//在全局數據區實例化一個FunctionTransfer的實例,該實例所在的縣城就是主線程。

FunctionTransfer main_thread_forward;
void FunctionTransfer::execinmain(std::tr1::function<void()> f)
{
    main_thread_forward.exec(f);
}
 
FunctionTransfer::FunctionTransfer(QObject *parent) :
    QObject(parent)
{
    connect(this,SIGNAL(comming(std::tr1::function<void()>)),this,SLOT(exec(std::tr1::function<void()>)),Qt::BlockingQueuedConnection);
}
 
 
void FunctionTransfer::exec(std::tr1::function<void()> f)
{
    if(Gt::isMainThread())
    {
        f();
    }
    else
    {
        emit this->comming(f);
    }
}

 

 

非常簡單的邏輯,如果在主線程就執行,如果不是在主線程就發給主線程,主線程接到之后就執行。

 

類有了,接下來考慮實用的場合,比如有一個類 A,A有個方法f不能再后台執行,需要跑到前台,怎么辦呢,上代碼:

 

作為參數的lamda表達式捕獲了類A的this指針,然后轉換為C++的函數對象,然后跑到前台去執行了,執行完成后才會返回,是不是灰常簡潔。

FunctionTransfer::execinmain([this](){this->f();});

 

http://my.oschina.net/fanhuazi/blog/737224


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM