Presumably, stopping the camera thread will be fast, and you shoudln't need to ignore the event. Simply wait until the camera thread finishes, and then return from the closeEvent
. The closeEvent
doesn't even need to get involved. You don't really care if the window was closed, you care that the application event loop has exited.
It's easy to determine when the camera thread finishes without having to interact with the frame grabber class. QtConcurrent::run
executes the functor on a thread taken from the global thread pool, a.k.a. QThreadPool::globalInstance()
. The activeThreadCount
method returns the number of threads that are busy doing work submitted to them.
You could implement it as follows:
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();
}
This of course presumes that no other threads from the global pool are stuck doing things that weren't signaled to stop. The CameraStopper
would need to issue stop signals to all such asynchronous tasks. Also ensure that if you've reserved any threads via reserveThread
, they are properly released before you get to wait for the camera to stop.
Another thing that you can do is to use some per-thread flag to stop the thread. Prior to Qt 5.2, such flag was only available if an QEventLoop
was running in the thread - calling QThread::quit()
on that thread's instance would end the event loop. Since Qt 5.2, there is another flag that's independent from the event loop. The flag is set via QThread::requestInterruption
, and checked via QThread::isInterruptionRequested
.
You need to keep a global list of threads, so that you can easily quit them all, since QThreadPool
doesn't expose its threads. This could be done so:
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());
}
};
Finally, the runnable would look as follows, for Qt 5.2 and up:
QtConcurrent::run([this]{
ThreadRegistration reg;
while (!QThread::currentThread()->isInterruptionRequested()) {
frame = captureFrame();
//Do stuff
}
}
For Qt 5.0 and 5.1, you could leverage a zero-timeout timer to run some code continuously as long as the event loop hasn't been quit:
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();
}
And the main
would looks as follows:
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
}