2013-12-17 48 views
5

我在Qt線程和連接方面遇到了一些麻煩。我發現了幾個關於這個主題的教程和討論,我遵循this tutorial來創建線程。但我仍然遇到了這個問題:在線程上調用wait()將永遠不會返回,並且UI會凍結。QThread :: wait()在不使用直接連接的情況下不會返回

類似的問題在這裏問(第二個例子)前: Qt connection type between threads: why does this work?

在問題的最後編輯,作者提到,他已經創造了一個僵局。我假設,我在我的申請中也是這樣做的。但我仍然不明白,爲什麼會發生這種情況。閱讀suggested article並沒有幫助我理解。我剛剛明白,僵局可能會發生,但我不知道,究竟是什麼原因造成了這種情況,或者是我的情況。

我也創建了一個簡化爲核心問題的例子。在這個問題的底部找到代碼。

所以我的問題是: 究竟是什麼原因造成我的例子中的死鎖? 有沒有一種解決方案,但沒有建立連接直接連接?

我真的很感激任何提示。

謝謝!

編輯:

因爲意見的我試了一下通過信號發送的停止請求和我說的線環QCoreApplication :: processEvents()調用。但主要問題仍然是一樣的。

EDIT2:

我發現了一個可接受的解決方案,想了多一點有關事件後循環:

thread.requestStop(); 

// now instead of using wait(), we poll and keep the event loop alive 
// polling is not nice, but if it does not take a very long time 
// for the thread to finish, it is acceptable for me. 
while (thread.isRunning()) 
{ 
    // This ensures that the finished() signal 
    // will be processed by the thread object 
    QCoreApplication::processEvents();   
} 

這實際工作和工人本身控制如何停止工作。

在提出這個問題後,我也對凍結問題做了一個解釋:調用等待似乎使主線程忙或暫停,因此它不處理任何事件。由於線程對象存在於主線程中,所以線程的完成()信號被設置,但從未處理。

我隱含的假設,即thread.wait()仍然會保持事件循環的工作,顯然是錯誤的。但是,那麼QThread :: wait()函數有什麼好處呢?!?

這只是一種理論,但也許這裏有人能證實或證僞它...

編輯3(最終的解決方案):

閱讀this small article和implmenting一個子類化的解決方案之後,我覺得這對於這個特定的問題是優選的。不需要事件循環,我可以直接調用另一個線程並使用互斥鎖保護。它代碼少,易於理解和調試。

我想我只會使用非子類化策略,如果與線程有更多的交互而不僅僅是啓動和暫停。


我減少實例

也許我應該指出的是,我不刪除線程,因爲我原來的應用程序,我想以後恢復,所以停止它實際上意味着暫停它。

worker.h:

#ifndef WORKER_H 
#define WORKER_H 

#include <QObject> 
#include <QMutex> 

class Worker : public QObject 
{ 
    Q_OBJECT 

public: 
    explicit Worker(QObject* parent = NULL); 

public slots: 
    void doWork(); 
    void requestStop(); 

signals: 
    void finished(); 

private: 

    bool stopRequested; 
    QMutex mutex; 
}; 

#endif // WORKER_H 

worker.cpp:

#include "worker.h" 

#include <QThread> 
#include <iostream> 

using namespace std; 

Worker::Worker(QObject *parent) 
    : stopRequested(false) 
{ 
} 

void Worker::doWork() 
{ 
    static int cnt = 0; 

    // local loop control variable 
    // to make the usage of the mutex easier. 
    bool stopRequesteLocal = false; 

    while (!stopRequesteLocal) 
    { 
     cout << ++cnt << endl; 
     QThread::msleep(100); 

     mutex.lock(); 
     stopRequesteLocal = stopRequested; 
     mutex.unlock(); 
    } 

    cout << "Finishing soon..." << endl; 

    QThread::sleep(2); 
    emit finished(); 
} 

void Worker::requestStop() 
{ 
    mutex.lock(); 
    stopRequested = true; 
    mutex.unlock(); 
} 

主程序:

#include <QCoreApplication> 
#include <QThread> 
#include <QtCore> 
#include <iostream> 

#include "worker.h" 

using namespace std; 

int main(int argc, char *argv[]) 
{ 
    QCoreApplication a(argc, argv); 
    QThread thread; 
    Worker worker; 


    QObject::connect(&thread, SIGNAL(started()), &worker, SLOT(doWork())); 

    // this does not work: 
    QObject::connect(&worker, SIGNAL(finished()), &thread, SLOT(quit())); 

    // this would work: 
    //QObject::connect(&worker, SIGNAL(finished()), &thread, SLOT(quit()), Qt::DirectConnection); 

    // relocating the moveToThread call does not change anything. 
    worker.moveToThread(&thread); 

    thread.start(); 

    QThread::sleep(2); 

    worker.requestStop(); 
    cout << "Stop requested, wait for thread." << endl; 
    thread.wait(); 
    cout << "Thread finished" << endl; 

    // I do not know if this is correct, but it does not really matter, because 
    // the program never gets here. 
    QCoreApplication::exit(0); 
} 
+0

'sleep'是Qt中的一個保護,你怎麼能調用QThread :: sleep(2);? – UmNyobe

+0

我不知道。我只是這樣做,它的工作。 ;) – Kanalpiroge

+0

這是不可能的:)你是否編輯過Qt源代碼? –

回答

2

我在問題文本中添加了我自己的回答,編輯爲3.

0

它看起來並不像你已經完全讀過那篇文章。

QThread* thread = new QThread; 
Worker* worker = new Worker(); 
worker->moveToThread(thread); 
connect(worker, SIGNAL(error(QString)), this, SLOT(errorString(QString))); 
connect(thread, SIGNAL(started()), worker, SLOT(process())); 
connect(worker, SIGNAL(finished()), thread, SLOT(quit())); 
connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater())); 
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); 
thread->start(); 

您只是部分實現了文章上的建議。

QThread::wait()將等到QThread::finished()doWork()發出。如果您需要退出此線程並返回到主線程,請發出此SIGNAL。爲此,您需要保留此對象移至的線程的引用。

+0

這似乎並不適用於我。如果我忽略該行,程序仍然在'thread.wait()'處阻塞。 – Kanalpiroge

+0

到您的編輯: 是的,因爲我不希望稍後刪除對象。但我試過這些東西只是爲了看看它是否有所作爲,但事實並非如此。我也嘗試使用指針而不是在線程上創建對象,但這也不會改變任何東西。 – Kanalpiroge

+0

完成()信號在循環結束後在doWork()的末尾發出。但是,wait()不會返回。但是我確定程序離開循環,因爲調用requestStop()後控制檯上顯示「即將完成...」。 – Kanalpiroge

2

我看到的第一個問題是,您沒有使用信號和插槽在不同線程上運行的對象之間進行通信; main和承載worker對象的新線程。

您移動中的工人對象,以第二個線程,但調用從主線程中的工人對象上的功能: -

thread.start(); 
QThread::sleep(2); 
worker.requestStop(); // Aaahh, this is running on the new thread!!! 

考慮到一個線程都有自己的堆棧和寄存器真不看看這是如何安全的。

如果您使用信號和插槽,Qt會處理很多線程問題。雖然您應該可以使用變量來控制第二個線程,但使用信號和插槽也會更清晰。

請注意,當信號從一個線程發出時,如果發送者和接收者位於不同的線程上,則會向接收對象的線程發送消息。

將您的代碼轉換爲使用信號和插槽在不同線程上的對象之間進行通信,並且您的死鎖應該消失。

+0

我認爲requestStop()函數是線程安全的,因爲互斥體。如果這真的是一個重點,它不能解釋爲什麼直接連接(這應該是壞的)工作,而排隊連接(這是你應該使用的)不會。 – Kanalpiroge

+1

互斥量可以保護函數中正在更改的變量,但不保護正在該函數中使用的堆棧和寄存器。通過一切手段,使用共享變量與互斥體,但我建議你調用工人對象上的插槽,而不是直接調用該函數。至於直接連接,我預計這只是運氣,稍後會引起你的問題。如果有人有不同的理解,我會對這個解釋如何工作感興趣。 – TheDarkKnight

+1

@Kanalpiroge梅林至少在這一點上是現貨。你需要通過信號發出請求。 – UmNyobe

相關問題