正确处理 QtT并发"infinite"循环

Correctly handling QtConcurrent with "infinite" loop

本文关键字:infinite 循环 并发 QtT 正确处理      更新时间:2023-10-16

我正在做一个程序,在这个程序中,用户可以看到来自摄像机的视频并记录下来。我正在使用QtMEL库来获取相机馈送和记录。当相机启动时,它调用一个带有QTconcurrent::run()的函数来抓取帧。在这个函数内部有一个无限循环,检查相机馈送是否关闭,如果是,则退出循环。像这样:

Q_FOREVER {
    frame = captureFrame();
    //Do stuff
    //check if we must finish grabbing
    if (isStopRequest() || isPauseRequest())
        break;
}

如果用户关闭相机(这会停止抓取帧并结束线程),然后存在程序,一切都很好。问题是当用户退出程序时,相机馈送仍然打开。如果发生这种情况,进程将永远运行或抛出分段错误。

我已经搜索了文档,并根据它:

注意QtConcurrent::run()返回的QFuture不支持取消、暂停或进度报告。返回的QFuture只能用于查询函数的运行/完成状态和返回值。

我想到了一个解决这个问题的方法,我不知道它是否是最好的,我希望从比我更有经验的人那里得到更多的见解。

基本上我像这样重新实现了closeEvent:

void MainWindow::closeEvent(QCloseEvent *event) {
    if(camera1->state() == AbstractGrabber::StoppedState)
        event->accept();
    else {
        camera1->stop();
        connect(camera1, SIGNAL(stateChanged(AbstractGrabber::State)),
            SLOT(exitWindow(AbstractGrabber::State)));
        event->ignore();
    }
}

在槽上:

void MainWindow::exitWindow(AbstractGrabber::State grabberState) {
    if(grabberState == AbstractGrabber::StoppedState) this->close();
}

也许我太天真了,但在我看来,有一个更好的解决方案。有经验的人能帮我吗?

提前感谢您的时间。

假设,停止相机线程将很快,并且您不应该忽略事件。只需等待,直到相机线程完成,然后从closeEvent返回。closeEvent甚至不需要参与。您并不真正关心窗口是否关闭,您关心的是应用程序事件循环是否已退出。

很容易确定相机线程何时结束,而无需与帧捕获器类交互。QtConcurrent::run在从全局线程池中取出的线程上执行函子,也就是QThreadPool::globalInstance()activeThreadCount方法返回正在忙于完成提交给它们的工作的线程数。

你可以这样实现它:

class CameraStopper() {
  Q_DISABLE_COPY(CameraStopper)
  AbstractGrabber m_camera;
public:
  explicit CameraStopper(AbstractGrabber * cam) : m_camera(cam) {}
  ~CameraStopper() {
    m_camera->stop();
    while (QThreadPool::globalInstance()->activeThreadCount())
      QThread::yieldCurrentThread();
  }
}
AbstractGrabber * MainWindow::camera() const { return camera1; }
int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  MainWindow w;
  CameraStopper stopper(w.camera());
  w.show();
  return app.exec();
}

当然,这是假设全局池中的其他线程没有被卡住而没有发出停止信号。CameraStopper需要向所有这样的异步任务发出停止信号。还要确保如果您通过reserveThread保留了任何线程,它们在您等待相机停止之前被正确释放。

你可以做的另一件事是使用一些每线程标志来停止线程。在Qt 5.2之前,这个标志只有在线程中运行QEventLoop时才可用——在该线程的实例上调用QThread::quit()将结束事件循环。自Qt 5.2以来,有另一个独立于事件循环的标志。该标志通过QThread::requestInterruption设置,并通过QThread::isInterruptionRequested检查。

您需要保持线程的全局列表,以便您可以轻松地退出它们,因为QThreadPool不公开其线程。可以这样做:

class ThreadRegistry {
  Q_DISABLE_COPY(ThreadRegistry)
  friend class ThreadRegistration;
  QMutex m_mutex;
  QList<QThread*> m_threads;
  static QScopedPointer<ThreadRegistry> m_instance;
  static ThreadRegistry * instance() {
    if (!m_instance) m_instance.reset(new ThreadRegistry);
    return m_instance;
  }
public:
  ThreadRegistry() {}
  ~ThreadRegistry() { quitThreads(); waitThreads(); }
  void quitThreads() {
    QMutexLocker lock(&m_mutex);
    QList<QThread*> & threads(m_threads);
    for (auto thread : threads) {
      thread->quit(); // Quit the thread's event loop, if any
      #if QT_VERSION>=QT_VERSION_CHECK(5,2,0)
      thread->requestCancellation();
      #endif
    }
  }
  void waitThreads() {
    forever {
      QMutexLocker lock(&m_mutex);
      int threadCount = m_threads.count();
      lock.unlock();
      if (!threadCount) break;
      QThread::yieldCurrentThread();
    }  
  }
};
class ThreadRegistration {
  Q_DISABLE_COPY(ThreadRegistration)
public:
  ThreadRegistration() {
    QMutexLocker lock(&ThreadRegistry::instance()->m_mutex);
    ThreadRegistry::instance()->m_threads << QThread::currentThread();
  }
  ~ThreadRegistration() {
    QMutexLocker lock(&ThreadRegistry::instance()->m_mutex);
    ThreadRegistry::instance()->m_threads.removeAll(QThread::currentThread());
  }
};

最后,Qt 5.2及以上版本的可运行文件如下:

QtConcurrent::run([this]{
  ThreadRegistration reg;
  while (!QThread::currentThread()->isInterruptionRequested()) {
    frame = captureFrame();
    //Do stuff
  }
}

对于Qt 5.0和5.1,您可以利用零超时计时器来连续运行一些代码,只要事件循环没有退出:

QtConcurrent::run([this]{
  ThreadRegistration reg;
  QTimer timer;
  timer.setInterval(0);
  timer.setSingleShot(false);
  timer.start();
  QObject::connect(&timer, &QTimer::timeout, [this]{
    frame = captureFrame();
    //Do stuff
  }
  QEventLoop loop;
  loop.run();
}

main看起来如下:

int main(int argc, char ** argv) {
  QApplication app(argc, argv);
  ThreadRegistry registry;
  MainWindow w;
  w.show();
  return app.exec();
  // ThreadRegistry's destructor will stop the threads 
  // and wait for them to finish
}

可以在类的析构函数中等待线程完成。这可以使用QFuture::waitForFinished ()来完成。您可以将线程的未来作为类成员:

QFuture<void> future;

并在新线程中运行函数:

future = QtConcurrent::run(this,&MyClass::myFunction);

在发送停止请求后的析构函数中,您可以等待线程完成:

camera1->stop();
if(future.isRunning())
    future.waitForFinished();

您的循环已经检查是否已关闭或暂停相机馈送。为什么不添加另一个检查来查看用户是否关闭了程序呢?

顺便说一下,QtConcurrent::run()QRunnable是为短期任务设计的。对于无限循环(永久线程),官方文档建议使用QThread: http://qt-project.org/doc/qt-5/threads-technologies.html

下面是QThread中循环的重新实现:

// CameraThread subclasses QThread
CameraThread::run()
{
    while (!isStopRequest()
            && !isPauseRequest()
            && !this->isInterruptionRequested())
    {
        frame = captureFrame();
        //Do stuff
    }
}

当你退出时,调用QThread::requestInterruption()使isInterruptionRequested()返回true并终止循环