2013-12-17 28 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

相关问题