2

我正在做一个程序,用户可以在其中看到来自摄像机的视频并记录下来。我正在使用 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();
}

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

在此先感谢您的时间。

4

3 回答 3

1

您的循环已经检查了摄像头供稿是否已关闭或暂停。为什么不添加另一个检查以查看用户是否关闭了程序?

顺便说一句,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()make isInterruptionRequested()return true 并打破你的循环。

于 2014-08-07T09:17:48.107 回答
1

据推测,停止相机线程会很快,你不应该忽略该事件。只需等到相机线程完成,然后从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 及更高版本,runnable 如下所示:

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
}
于 2014-08-06T23:15:36.980 回答
1

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

QFuture<void> future;

并通过以下方式在新线程中运行您的函数:

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

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

camera1->stop();

if(future.isRunning())
    future.waitForFinished();
于 2014-08-07T05:23:37.750 回答