【问题标题】:Worker thread stops processing events during infinite loop in Qt工作线程在 Qt 中的无限循环期间停止处理事件
【发布时间】:2018-12-30 09:56:14
【问题描述】:

我在 Qt 中创建了一个 Worker 对象来无限期地处理视频输入,然后将其移动到 QThread 以保持 UI 线程继续运行。问题是,我将其设计为使视频捕获功能在无限循环中运行,直到被标志中断,并且该标志应该由 Worker 对象中的插槽设置,但由于 Worker 对象位于无限循环内,它永远不会处理这个“退出”插槽(或者至少我认为这是正在发生的事情)。我依赖于一个外部库,因此将视频轮询替换为另一种方法并不是一个真正的选择。有人可以确认这确实是问题并提出解决方案吗?代码如下:

class worker : public QObject{
    Q_OBJECT
public:
    worker(QObject* parent = NULL);
    ~worker(){}
    Q_SLOT void process();
    Q_SLOT void stop();
private:
    bool quit;
};

worker::worker(QObject *parent) : QObject(parent){
    quit = false;
}
void worker::process(){
    while(!quit){
        //this library call puts the thread to sleep until a frame is available
        WaitForVideoFrame();
    }
}
void worker::stop(){
    quit = true;
}

然后从我拥有的 UI 对象:

MyWorker = new worker();
QThread* thread = new QThread;
MyWorker->moveToThread(thread);
connect(thread, SIGNAL(started()), MyWorker, SLOT(process()));
QPushButton* stop_button = new QPushButton(this);
connect(stop_button, SIGNAL(clicked(bool)), MyWorker, SLOT(stop()));
thread->start();

这里的问题是,当我按下 stop_button 时,没有任何反应,工作人员继续运行循环。是否有可能我可以调用一个函数来从无限循环中为事件循环产生处理时间?或者对此有更好的设计/解决方案?欢迎提出任何建议。

【问题讨论】:

  • 这个线程运行了多长时间?
  • 它可能会运行一整天,Jon。 8、10 小时不间断。

标签: multithreading qt qt5 signals-slots qthread


【解决方案1】:

使用事件调度器

首先,这是QAbstractEventDispatcher 上的文档,它使排队信号和线程间事件传播工作。

thread()->eventDispatcher()->processEvents();

在循环中的某处添加上面的行,以便在适当的时间间隔调用它。它检索当前线程,然后是线程的事件调度程序,最后强制调度程序处理任何未决事件(包括传入排队信号)。

使用线程安全的方法发出信号停止

您可以调用拥有线程的requestInterruption() 函数,而不是使用线程不安全标志。

随后,您可以致电QThread::isInterruptionRequested()。如果它返回 true,您可以假设是时候停止做您正在做的任何工作了。

您的循环最终将如下所示:

void worker::process(){
    while(!thread->isInterruptionRequested()){
        thread()->eventDispatcher()->processEvents();
        if (thread()->isInterruptionRequested())
            break;
        WaitForVideoFrame();
    }
}

您在此循环中有两个可能长时间运行的调用,即处理事件的调用和WaitForVideoFrame()。我已经设置了循环以检查它是否应该在两者之后退出。标准免责声明适用:在您的用例中测试其性能(具体而言,在循环中检查两次中断请求可能过于激进)。

你应该继承 QThread 吗?

来自cmets:moveToThread vs deriving from QThread in Qt

【讨论】:

  • 感谢大家的反馈和宝贵的讨论。我决定接受 Jon Harper 的建议,因为我确实需要插槽机制(我正在使用信号发送视频帧),以及其他事情。但是所有的建议让我对不同的方法及其用途有了一个清晰的了解,谢谢大家!
【解决方案2】:

你的假设是正确的。每个QThread 都有自己的QEventLoop,当您在不同线程上发出信号时,它将在目标QEventLoop 中排队等待处理。一旦您的工作人员的QEventLoop 正在处理该事件,任何其他发出的信号都将在您完成当前槽后排队和处理。

不过,解决这个问题应该不会太难;这需要小心一点,但您可以安全地从QThread 派生,并使这个长轮询工作。如果您不需要可恢复操作 QThread 甚至有一个属性会向您指示已请求中断 - 请检查 QThread::isInterruptionRequested(),例如:

class mythread : public QThread {
    Q_OBJECT

public:
    using QThread::QThread;

    void run() override { // Main loop
        while (!isInterruptionRequested()) {
           //this library call puts the thread to sleep until a frame is available
           WaitForVideoFrame();
        }
    }
}

但是您确实需要注意正确的关闭顺序(大概在您的 MainWindow 析构函数中):

void stopThread() {
    if (m_thread != nullptr) {
        m_thread->requestInterruption();
        m_thread->exit();
        m_thread->wait();
    }
}

编辑:上面的示例假设您不需要完全工作的信号和插槽;如果你这样做了,你可以在等待时更新run() 方法来处理它:

class mythread : public QThread {
    Q_OBJECT

public:
    using QThread::QThread;

signals:
    void alive();

public slots:
    void onTimeout() { qDebug() << "timeout!"; }

    void run() override {  // Main loop
        QTimer timer;
        timer.setInterval(1500);
        QObject::connect(&timer, &QTimer::timeout, this, &mythread::alive);
        timer.start();

        while (!isInterruptionRequested()) {
            //this library call puts the thread to sleep until a frame is available
            WaitForVideoFrame();

            // Process event queue
            eventDispatcher()->processEvents(QEventLoop::AllEvents);
        }
    }
};

然后你就可以按照通常的方式连接它了:

mythread t;
t.start();

QTimer timer;
timer.setInterval(1000);

QObject::connect(&t, &mythread::alive, [] {
    qDebug() << "I'm alive";
});

QObject::connect(&timer, &QTimer::timeout, &t, &mythread::onTimeout);

timer.start();

【讨论】:

  • 谢谢你的建议,shrpq。不过,我可以问一下,这与您不应该从 QThread 派生来实现处理而只添加线程功能的准则有何关系?直接从 QThread 派生会遇到某种问题吗?
  • @ArmandoMartins 是的,您会遇到问题。上面的实现不会导致事件循环处理事件,这意味着排队信号和线程间事件永远不会传播。您的标志也应该是原子的或受互斥体保护。
  • 您的mythread 实现缺少事件循环,因为run 的默认实现调用exec,这是启动事件循环的地方!
  • @ArmandoMartins 继承 QThread 是完全安全和有效的,如果您不需要 Qt 事件处理,则永远不要运行事件循环。参照。 woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 例如。在 Qt 的 mandelbrot 示例中使用:doc.qt.io/qt-5/qtcore-threads-mandelbrot-example.html
  • 哎呀,也是真的。我并不是要暗示你永远不应该继承QThread,它有自己的用途。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-03
  • 1970-01-01
  • 2014-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多