From 418dd5e126a8782ae8e7d237d7e9888101b12879 Mon Sep 17 00:00:00 2001 From: Mathew Sutcliffe Date: Fri, 3 Oct 2014 01:01:27 +0100 Subject: [PATCH] refs #325, CWorker class for executing arbitrary tasks in their own threads --- src/blackmisc/worker.cpp | 46 +++++++++++++ src/blackmisc/worker.h | 143 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 src/blackmisc/worker.cpp create mode 100644 src/blackmisc/worker.h diff --git a/src/blackmisc/worker.cpp b/src/blackmisc/worker.cpp new file mode 100644 index 000000000..034e22950 --- /dev/null +++ b/src/blackmisc/worker.cpp @@ -0,0 +1,46 @@ +/* Copyright (C) 2014 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "worker.h" + +namespace BlackMisc +{ + + CWorker *CWorker::fromTask(QObject *owner, const QString &name, std::function task) + { + auto *thread = new CRegularThread(owner); + auto *worker = new CWorker(task); + + QString ownerName = owner->objectName().isEmpty() ? owner->metaObject()->className() : owner->objectName(); + thread->setObjectName(ownerName + ":" + name); + worker->setObjectName(name); + + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, "ps_runTask"); + thread->start(); + + return worker; + } + + void CWorker::ps_runTask() + { + m_task(); + + QMutexLocker lock(&m_finishedMutex); + m_finished = true; + emit finished(); + lock.unlock(); + + auto *ownThread = thread(); + moveToThread(ownThread->thread()); // move worker back to the thread which constructed it, so there is no race on deletion + QMetaObject::invokeMethod(ownThread, "deleteLater"); + QMetaObject::invokeMethod(this, "deleteLater"); + } + +} diff --git a/src/blackmisc/worker.h b/src/blackmisc/worker.h new file mode 100644 index 000000000..7fadf8495 --- /dev/null +++ b/src/blackmisc/worker.h @@ -0,0 +1,143 @@ +/* Copyright (C) 2014 + * swift Project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_WORKER_H +#define BLACKMISC_WORKER_H + +#include +#include +#include + +namespace BlackMisc +{ + + /*! + * Just a subclass of QThread whose destructor waits for the thread to finish. + */ + class CRegularThread : public QThread + { + public: + //! Constructor + CRegularThread(QObject *parent = nullptr) : QThread(parent) {} + + //! Destructor + ~CRegularThread() + { + quit(); + wait(); + } + }; + + /*! + * Class for doing some arbitrary task in its own thread. + * + * The task is exposed as a function object, so could be a lambda or a hand-written closure. + * CWorker can not be subclassed, instead it can be extended with rich callable task objects. + */ + class CWorker final : public QObject + { + Q_OBJECT + + public: + /*! + * Returns a new worker object which lives in a new thread. + * \param owner Will be the parent of the new thread (the worker has no parent). + * \param name A name for the task, which will be used to create a name for the thread. + * \param task A function object which will be run by the worker in its thread. + */ + static CWorker *fromTask(QObject *owner, const QString &name, std::function task); + + //! Connects to a slot which will be called when the task is finished. + //! \threadsafe + template + auto then(T *receiver, F slot) -> typename std::enable_if::value>::type + { + Q_ASSERT(receiver->thread() == QThread::currentThread()); + QMutexLocker lock(&m_finishedMutex); + connect(this, &CWorker::finished, receiver, slot); + if (m_finished) { (receiver->*slot)(); } + } + + //! Connects to a functor which will be called when the task is finished. + //! \threadsafe + template + auto then(T *context, F functor) -> typename std::enable_if::value>::type + { + Q_ASSERT(context->thread() == QThread::currentThread()); + QMutexLocker lock(&m_finishedMutex); + connect(this, &CWorker::finished, context, functor); + if (m_finished) { functor(); } + } + + //! Connects to a functor which will be called when the task is finished. + //! \threadsafe + template + void then(F functor) + { + QMutexLocker lock(&m_finishedMutex); + connect(this, &CWorker::finished, functor); + if (m_finished) { functor(); } + } + + //! Returns true if the task has finished. + //! \threadsafe But don't rely on this condition remaining true for any length of time. + bool isFinished() const + { + QMutexLocker lock(&m_finishedMutex); + return m_finished; + } + + //! Executes some code (in the caller's thread) if the task has finished. + //! \threadsafe + template + bool doIfFinished(F functor) const + { + QMutexLocker lock(&m_finishedMutex); + if (m_finished) { functor(); } + } + + //! Executes some code (in the caller's thread) if the task has not finished. + //! \threadsafe + template + bool doIfNotFinished(F functor) const + { + QMutexLocker lock(&m_finishedMutex); + if (! m_finished) { functor(); } + } + + //! Executes some code (in the caller's thread) if the task has not finished and some different code if it has finished. + //! \threadsafe + template + bool doIfFinishedElse(F1 ifFunctor, F2 elseFunctor) const + { + QMutexLocker lock(&m_finishedMutex); + if (m_finished) { ifFunctor(); } else { elseFunctor(); } + } + + signals: + //! Emitted when the task is finished. + void finished(); + + private slots: + //! Called when the worker has been moved into its new thread. + void ps_runTask(); + + private: + CWorker(std::function task) : m_task(task) {} + + bool m_finished = false; + mutable QMutex m_finishedMutex { QMutex::Recursive }; + std::function m_task; + }; + +} + +#endif