Qt 阻塞事件循环

Qt blocked event loop

我最近开始使用QT框架。昨天我开始编写一个简单的多线程应用程序。目前我有点卡在以下问题上。

考虑两个 worker classes,它们都使用一个线程来做一些事情 'heavy computations'。第一个 class,FooWorker,如下所示:

class FooWorker : public QObject
{
    Q_OBJECT

public:
    FooWorker() : QObject() { }
    ~FooWorker() { }

signals:
    void notify(int);
    void aborted();

public slots:
    void doWork()
    {
        int counter = 0;
        forever {

            // For the sake of this example this reassembles a heavy computational process
            if(counter++ < 10) {
                emit notify(counter);
                QThread::sleep(1);
            } else {
                counter = 0;

                // Wait until we get a signal to restart the process
                mutex_.lock();
                condition_.wait(&mutex_);
                mutex_.unlock();
            }
            // We should check for a cancellation flag every iteration...
        }

        emit aborted();
    }

private:
    QMutex mutex_;
    QWaitCondition condition_;
};

插槽 'doWork' 将被安排到另一个线程中的 运行。该插槽将永远 运行 并且每秒发出一个信号,直到发出 10 个通知。之后我们等到它再次被唤醒。

第二个 class,BarWorker,看起来像这样:

class BarWorker : public QObject
{
    Q_OBJECT

public:
    BarWorker() : QObject() { }
    ~BarWorker() { }

signals:
    void aborted();

public slots:
    void doWork()
    {
        forever {
            // Another heavy computational process
            QThread::sleep(1);

            // We should check for a cancellation flag every iteration...
        }

        emit aborted();
    }

    void onNotify(int value)
    {
        qDebug() << "Notification value:" << value;
    }
};

插槽 'doWork' 将再次安排到另一个线程中的 运行。该插槽将 运行 永远做一个繁重的计算过程。一旦该过程完成,我们将再次等待,直到它再次被唤醒(为了这个例子,我在这个 class 中省略了它)。

最后主图如下:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThread* barThread = new QThread();
    BarWorker* barWorker = new BarWorker();
    barWorker->moveToThread(barThread);

    QThread* fooThread = new QThread();
    FooWorker* fooWorker = new FooWorker();
    fooWorker->moveToThread(fooThread);

    // Automatically deletes worker and thread
    QObject::connect(fooThread, SIGNAL(started()), fooWorker, SLOT(doWork()));
    QObject::connect(fooWorker, SIGNAL(aborted()), fooThread, SLOT(quit()));
    QObject::connect(fooWorker, SIGNAL(aborted()), fooWorker, SLOT(deleteLater()));
    QObject::connect(fooThread, SIGNAL(finished()), fooThread, SLOT(deleteLater()));

    QObject::connect(barThread, SIGNAL(started()), barWorker, SLOT(doWork()));
    QObject::connect(barWorker, SIGNAL(aborted()), barThread, SLOT(quit()));
    QObject::connect(barWorker, SIGNAL(aborted()), barWorker, SLOT(deleteLater()));
    QObject::connect(barThread, SIGNAL(finished()), barThread, SLOT(deleteLater()));

    QObject::connect(fooWorker, SIGNAL(notify(int)), barWorker, SLOT(onNotify(int)), Qt::QueuedConnection);

    fooThread->start();
    barThread->start();

    return a.exec();
}

当我 运行 应用程序时,不会打印任何内容。这是意料之中的,因为 BarWorker 实例的事件循环被阻塞了。随着 'notify' 信号被发出,'onNotify' 槽被排入事件队列。因为我们在 'doWork' 插槽中有一个永无止境的循环(直到我们手动中止它),所以不会调用 'onNotify' 插槽。为了解决这个问题,我可以做几件事,即:

  1. 使用 Qt::DirectConnection 标志将 'notify' 信号连接到 'onNotify' 插槽。这样看起来就像在信号线程上执行的普通函数调用。
  2. 偶尔调用QCoreApplication::processEvents()方法强制处理事件队列
  3. 未知解决方案我现在不知道:)???

我希望有人对此问题有一些替代解决方案,或者甚至建议一种完全不同的方法,因为恕我直言,上述解决方案有些丑陋并且感觉不对。

我认为这里找不到任何 "magic" 解决方案;如果线程 运行ning 您自己的自定义事件循环,则它不能 运行ning Qt 的事件循环。在实践中,有两种常见的解决方案,它们实际上是一枚硬币的两面:

  1. 按照您在问题中的建议,定期从您的事件循环中调用 processEvents(),以便 Qt 事件处理代码偶尔到达 运行 并处理传入的异步信号。

  2. 不要在您的 doWork() 方法中使用长 运行ning 循环。相反,做少量的工作,将该工作的 results/state 存储在成员变量或某处,然后调用类似 QTimer::singleShot(0, this, SLOT(doWork())) 的东西,以便Qt 事件循环将在第一次调用 doWork() returns 后不久再次调用您的 doWork() 方法。这样,Qt 事件循环的延迟时间永远不会超过单个 doWork() 调用所占用的(短暂)时间段。

在这两个选项中,我认为第二个更可取,因为它允许 Qt 事件循环以其正常方式 运行,并且它还避免了潜在的绊倒你自己的鞋带问题——例如想象一下,如果在使用解决方案 (1) 时,您对 processEvents() 的调用导致调用删除 BarWorker 对象的插槽。当 processEvents() 调用 returns、BarWorker::doWork() 将恢复执行时,但此时,作为其正常执行的一部分可能访问的所有本地成员变量和虚拟方法都已被销毁,读取或写入它们会导致未定义的行为(如果幸运的话,会导致易于调试的崩溃)。使用解决方案 (2) 时不会发生这种可能的混乱,因为如果 BarWorker 对象在调用 doWork() 之间被删除,任何排队的 doWork() 异步调用将被安全地取消。

与事件循环互操作的 forever 循环的惯用语是零持续时间计时器。我们可以把它分解成一个WorkerBaseclass,这里的工作单元是在workUnit方法中完成的:

// https://github.com/KubaO/Whosebugn/tree/master/questions/worker-timer-40369716
#include <QtCore>

// See 
template <typename Fun> void safe(QObject * obj, Fun && fun) {
    Q_ASSERT(obj->thread() || qApp && qApp->thread() == QThread::currentThread());
    if (Q_LIKELY(obj->thread() == QThread::currentThread()))
        return fun();
    struct Event : public QEvent {
      using F = typename std::decay<Fun>::type;
      F fun;
      Event(F && fun) : QEvent(QEvent::None), fun(std::move(fun)) {}
      Event(const F & fun) : QEvent(QEvent::None), fun(fun) {}
      ~Event() { fun(); }
    };
    QCoreApplication::postEvent(
          obj->thread() ? obj : qApp, new Event(std::forward<Fun>(fun)));
}

class WorkerBase : public QObject {
    Q_OBJECT
    QBasicTimer timer_;
protected:
    virtual void workUnit() = 0;
    void timerEvent(QTimerEvent *event) override {
        if (event->timerId() == timer_.timerId() && timer_.isActive())
            workUnit();
    }
public:
    using QObject::QObject;
    Q_SIGNAL void finished();
    /// Thread-safe
    Q_SLOT void virtual start() {
        safe(this, [=]{
           timer_.start(0, this);
        });
    }
    /// Thread-safe
    Q_SLOT void virtual stop() {
        safe(this, [=]{
            if (!isActive()) return;
            timer_.stop();
            emit finished();
        });
    }
    bool isActive() const { return timer_.isActive(); }
    ~WorkerBase() {
        if (isActive()) emit finished();
    }
};

然后工人变成:

class FooWorker : public WorkerBase
{
    Q_OBJECT
    int counter = 0;
    bool isDone() const { return counter >= 10; }
    void workUnit() override {
        if (!isDone()) {
            counter ++;
            emit notify(counter);
            QThread::sleep(1);
        } else
            stop();
    }
public:
    void start() override {
        counter = 0;
        WorkerBase::start();
    }
    void stop() override {
        if (!isDone()) emit aborted();
        WorkerBase::stop();
    }
    Q_SIGNAL void notify(int);
    Q_SIGNAL void aborted();
};

class BarWorker : public WorkerBase
{
    Q_OBJECT
    void workUnit() override {
        QThread::sleep(1);
    }
public:
    void stop() override {
        emit aborted();
        WorkerBase::stop();
    }
    Q_SIGNAL void aborted();
    Q_SLOT void onNotify(int value)
    {
        qDebug() << "Notification value:" << value;
    }
};

请注意,aborted()finished() 信号具有不同的含义。

最后,测试线束:

class Thread : public QThread { public: ~Thread() { quit(); wait(); } };

int main(int argc, char ** argv) {
    QCoreApplication app{argc, argv};

    BarWorker barWorker;
    FooWorker fooWorker;
    Thread barThread, fooThread;
    barWorker.moveToThread(&barThread);
    fooWorker.moveToThread(&fooThread);
    barWorker.start();
    fooWorker.start();

    QObject::connect(&fooWorker, &FooWorker::finished, &app, &QCoreApplication::quit);
    QObject::connect(&fooWorker, &FooWorker::notify, &barWorker, &BarWorker::onNotify);

    fooThread.start();
    barThread.start();
    return app.exec();
}

#include "main.moc"

如果您收到 QBasicTimer::stop: Failed. Possibly trying to stop from a different thread 警告,这无关紧要,是 Qt 错误的结果。