【问题标题】:QApplication thread freezes because of another QThreadQApplication 线程因为另一个 QThread 而冻结
【发布时间】:2013-10-31 08:46:11
【问题描述】:

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

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

示例代码如下。主线程调用 update() 时输出“Main”,工作线程调用“Worker”。如果您运行它,您会看到定期打印“Worker”,并且“Main”恰好出现两次(开始时出现一次,大约 5 秒后出现一次)。如果是功能齐全的 GUI 应用程序,这实际上意味着 GUI 完全冻结。

一些观察。

  1. 通过对内部循环设置 100 个限制(而不是 1000 个)来减少工作量将解决问题(两个 update() 方法都将被定期调用)。
  2. 将工作线程计时器信号的连接类型设置为 Qt::DirectConnection 将解决此问题。

因此,如您所见,我对此有几个解决方法,但如果有人向我解释原始代码有什么问题,我将不胜感激。我希望线程独立执行它们的事件循环。我知道我通过长时间的 update() 操作阻塞了工作线程事件循环,但是为什么它会影响主线程呢?

附:是的,我知道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() 只是一些过程,它是一个 slot。一个特定对象的槽,它有自己的线程亲和性,这意味着它的主体将在该线程中执行(除非使用 Qt::DirectConnection 调度信号)。现在,看来我已经用计时器做得很好了——它们中的每一个都属于不同的线程——但是用 update() 把事情搞砸了。所以我最终在主线程中执行了两个 update() 过程。显然在某些时候事件循环被定时器事件淹没并且永远不会完成迭代。

至于解决方案。如果您阅读过"You're doing it wrong"(并且您确实应该),您就会知道将所有逻辑实现在一个不是从 QThread 子类化而是单独创建并使用 moveToThread() 附加到 QThread 的对象中是相当方便的。如果您记住您的对象仅控制线程但不属于线程,我个人认为从 QThread 子类化没有任何错误。所以它不是你想在那个线程中执行的代码的地方。

【问题讨论】:

    标签: multithreading qt qthread qtcore


    【解决方案1】:

    这里的第一个问题是你从 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 继承,然后将该对象移动到新线程。

    【讨论】:

    • 谢谢。实际上,我已经多次阅读所有这些帖子,例如“你做错了”=)但直到现在我才开始明白。所以我的 MyThread 对象驻留在主线程中,发送给它的计时器事件进入主线程的事件循环。凉爽的。现在,我想知道为什么主线程的 update() 几乎没有机会?我的意思是两个计时器的间隔相等,它们应该正面交锋。
    • 您可以从 MyObject 和 MyThread 的各个点打印线程的地址,例如构造函数、运行和更新函数。这将使您更好地了解线程亲和性方面的情况只需调用 printf("0x%x", obj->thread());但总的来说,如果您只是重构而不从 QThread 继承,您可能会看到它按预期工作。
    • 作为“你做错了”文章的补充,这是关于如何使用线程的一个很好的后续:mayaposch.wordpress.com/2011/11/01/…
    • 我正准备将您的帖子标记为答案,但仔细阅读会发现一些误解。你已经把它弄乱了定时器的亲和力,但实际上情况并非如此。我的计时器没问题。错位的是 MyThread::update() 插槽。本以为会在timer所属的线程中执行,但自然是在MyThread所属的线程(主线程)中执行。
    • @AntonZherzdev 如果您打算使用插槽,则永远不应该继承 QThread。正如您所注意到的,插槽将在您的 MyThread 对象所在的线程中执行,而不是在它管理的线程中执行。它也是stated in the docsimplementing new slots in a QThread subclass is error-prone and discouraged
    【解决方案2】:

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

    从你的主要我看不到像GUI 这样的东西......你只是因为任何原因而不是app_-&gt;exec()(?)调用QApplication::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 框架应该在需要时重绘。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-25
      • 1970-01-01
      • 1970-01-01
      • 2017-10-02
      • 2020-10-12
      • 2018-07-20
      • 2023-04-06
      相关资源
      最近更新 更多