工作线程在Qt中的无限循环期间停止处理事件

时间:2018-07-23 01:06:47

标签: multithreading qt qt5 signals-slots qthread

我已经在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时,什么也没发生,工作程序继续运行循环。我是否可以调用一个函数来从无限循环内为事件循环产生处理时间?或对此有更好的设计/解决方案?欢迎任何建议。

2 个答案:

答案 0 :(得分:0)

您的假设正确。每个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();
    }
}

EDIT :以上示例在假设您不需要完全正常工作的信号和插槽的情况下工作;如果您愿意,可以更新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();

答案 1 :(得分:0)

使用事件分派器

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

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

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

使用线程安全的方法来表示该停止了

可以使用拥有线程的requestInterruption()函数来代替线程不安全标志。

随后,您可以呼叫QThread::isInterruptionRequested()。如果返回true,则可以假定是时候停止进行任何工作了。

您的循环最终将看起来像这样:

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

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

您应该继承QThread吗?

与评论相关:moveToThread vs deriving from QThread in Qt