由于另一个QThread,QApplication线程冻结

时间:2013-10-22 12:57:57

标签: multithreading qt qthread qtcore

在我的Qt应用程序中,我创建了一个QThread,它应该定期执行一些繁重的计算任务。主QApplication线程应该维护GUI(不包括在示例中)并执行一些定期更新。两个线程都有自己的定时器来启用常规的update()调用。

问题:当工作线程的计算工作负载超过某个临界值时,我的主线程停止接收计时器事件。

示例代码如下。当主线程调用update()时,它输出“Main”,工作线程调用“Worker”。如果你运行它,你会看到“工人”定期打印,“主”正好出现两次(一个在开头,一个在约5秒)。对于功能齐全的GUI应用程序,这实际上意味着完全冻结GUI。

一些观察结果。

  1. 通过对内循环(而不是1000)设置100限制来减少工作量将解决问题(将定期调用update()方法)。
  2. 将工作线程定时器信号的连接类型设置为Qt :: DirectConnection将解决问题。
  3. 所以,正如你所看到的,我对此有一些解决方法,但我很感激有人解释我原始代码的问题是什么。我希望线程能够独立地执行它们的事件循环。我知道我通过一个很长的update()操作来阻止工作线程事件循环,但为什么它会影响主线程?

    P.S。是的,我知道QConcurrent替代方案。但我只是想明白。

    TEST.CPP

    #include <windows.h>
    #include <QApplication>
    
    #include "test.h"
    
    HANDLE mainThread_ = INVALID_HANDLE_VALUE;
    QApplication *app_ = 0;
    MyObj *obj_ = 0;
    MyThread *thread_ = 0;
    
    MyObj::MyObj()
        : timer_(0)
    {
        timer_ = new QTimer(0);
        connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
        timer_->start(10);
    }
    
    void MyObj::update()
    {
        printf("Main\n");
    }
    
    void MyThread::run()
    {
        timer_ = new QTimer(0);
        connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
        timer_->start(10);
    
        exec();
    }
    
    void MyThread::update()
    {
        printf("Worker\n");
    
        // do some hard work
        float f = 0.f;
        for (int i=0; i < 100000; ++i)
        {
            for (int j=0; j < 1000; ++j)
            {
                f += i * j;
            }
        }
    }
    
    int main()
    {
        int argc = 0;
        app_ = new QApplication(argc, 0);
    
        obj_ = new MyObj();
        thread_ = new MyThread();
        thread_->start();
    
        QApplication::exec();
    
        return 0;
    }
    

    test.h

    #include <QTimer>
    #include <QThread>
    
    class MyObj : public QObject
    {
        Q_OBJECT
    
    public:
        MyObj();
    
    public slots:
        void update();
    
    private:
        QTimer *timer_;
    };
    
    class MyThread : public QThread
    {
        Q_OBJECT
    
    public:
        void run();
    
    public slots:
        void update();
    
    private:
        QTimer *timer_;
    };
    

    UPD:我从尊敬的成员那里得到了一些答案(请在下面阅读)。现在我想澄清一下错误的想法特别破坏了我的代码。

    正如您所看到的,该计划有两个线程,每个线程定期运行一些update()过程。我的错误是将update()视为一些程序,它是一个插槽。特定对象的一个​​槽,它有自己的线程关联,这意味着它的主体将在该线程中执行(除非用Qt :: DirectConnection调度信号)。现在,似乎我已经用计时器完成了它们 - 每个都属于不同的线程 - 但是用update()搞砸了。所以我最终在主线程中执行了两个update()程序。显然,在某些时候,事件循环会被定时器事件淹没,并且永远不会完成迭代。

    至于解决方案。如果您已经阅读"You're doing it wrong"(并且您确实应该),您知道将所有逻辑实现在一个对象中是非常方便的,该对象不是从QThread继承而是单独创建并使用moveToThread()连接到QThread。就个人而言,如果你记住你的对象只有控制线程但不属于,那么我从QThread中看不到任何错误的子类。因此,它不是您希望在该线程中执行的代码的位置。

2 个答案:

答案 0 :(得分:3)

这里的第一个问题是你继承自QThread,因为它表示here, "you're doing it wrong".

您遇到的问题源于线程关联(运行对象的线程)。例如,如果您要从QThread继承并在构造函数中创建对象,而不对该对象进行父对象,则该对象将在主线程中运行,而不是在新线程中运行。所以在MyThread构造函数中你有: -

MyThread::MyThread()
    : timer_(0)
{
    timer_ = new QTimer(0);
    connect(timer_, SIGNAL(timeout()), this, SLOT(update()));
    timer_->start(10);
}

此处的计时器(timer_)将在主线程上运行,而不是新线程。 为了避免重复我自己,我以前的一个答案解释了thread affinity here

解决问题的最佳方法是将类更改为继承自QObject,然后将该对象移动到新线程。

答案 1 :(得分:2)

首先...... http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/

从你的主要我看到的只是GUI ...你只是因为QApplication::exec()(?)

的原因而打电话给app_->exec()

您的线程问题: 您可以创建一个具有插槽doUpdate()左右的QObject派生类。你可以这样做:

TheUpdateObject* obj = new TheUpdateObject;
obj->moveToThread(thread_);  // thread_ is a QThread object
thread_->start();
connect(thread_, SIGNAL(finished()), obj, SLOT(deleteLater()));
QTimer* tmr = new QTimer(this);
tmr->setTimeout(10);
connect(tmr, SIGNAL(timeout()), obj, SLOT(doUpdate()));
connect(tmr, SIGNAL(timeout()), tmr, SLOT(start()));
tmr->start();

所以定时器应该重新启动,并且应该在另一个线程中调用doUpdate()

在GUI内部,您不需要检查更新,qt框架应该在需要时重绘。