diff --git a/lib/core/CMakeLists.txt b/lib/core/CMakeLists.txt index 61cd27e5..29cc548b 100644 --- a/lib/core/CMakeLists.txt +++ b/lib/core/CMakeLists.txt @@ -68,6 +68,8 @@ qx_add_component("Core" qx-systemerror.cpp qx-systemerror_linux.cpp qx-systemerror_win.cpp + __private/qx-generalworkerthread.h + __private/qx-generalworkerthread.cpp __private/qx-internalerror.cpp __private/qx-processwaiter.h __private/qx-processwaiter.cpp diff --git a/lib/core/src/__private/qx-generalworkerthread.cpp b/lib/core/src/__private/qx-generalworkerthread.cpp new file mode 100644 index 00000000..f2c359d8 --- /dev/null +++ b/lib/core/src/__private/qx-generalworkerthread.cpp @@ -0,0 +1,87 @@ +// Unit Include +#include "qx-generalworkerthread.h" + +// Qt Includes +#include + +/*! @cond */ +namespace Qx +{ + +//=============================================================================================================== +// GeneralWorkerThread +//=============================================================================================================== + +//-Constructor-------------------------------------------------------------------- +//Public: +GeneralWorkerThread::GeneralWorkerThread() : + mWorkerCount(0) +{ + /* mThread is the only QObject member of this class. We need to move it to the main thread because + * the manager can be created in any thread since it's done by RAII and UB occurs if a Object (in this case + * the QThread) continues to be used if the thread it belongs to is shutdown. moveToThread() already checks + * if this is the main thread and results in a no-op if so. Also, QThread's public methods are protected + * by a mutex, so it's safe to interact with it from which ever thread is accessing the manager. + */ + QThread* mainThread = QCoreApplication::instance()->thread(); + if(!mainThread) [[unlikely]] + { + // It's documented that you're not supposed to use QObjects before QCoreAppliation is created, + // but check explicitly anyway + qCritical("Cannot use QObject's before QCoreApplication is created!"); + } + + mThread.moveToThread(mainThread); +} + +//-Destructor-------------------------------------------------------------------- +//Public: +GeneralWorkerThread::~GeneralWorkerThread() +{ + if(mThread.isRunning()) + stopThread(true); +} + +//-Instance Functions-------------------------------------------------------------------------------------------- +//Private: +void GeneralWorkerThread::startThread() +{ + Q_ASSERT(!mThread.isRunning()); + mThread.start(QThread::LowPriority); // The work here should be on the lighter side +} + +void GeneralWorkerThread::stopThread(bool wait) +{ + Q_ASSERT(mThread.isRunning()); + mThread.quit(); + if(wait) + mThread.wait(); +} + +void GeneralWorkerThread::workerDestroyed() +{ + if(!--mWorkerCount) + stopThread(); +} + +//Public: +void GeneralWorkerThread::moveTo(QObject* object) +{ + if(!mWorkerCount++) + startThread(); + + object->moveToThread(&mThread); + + // Worker management + + /* Have worker killed if it still exists when thread is being shutdown; + * + * QThread docs note that deferred deletions still occur after finished is emitted, so this is possible + * (this use case is explicitly mentioned). + */ + QObject::connect(&mThread, &QThread::finished, object, &QObject::deleteLater); + QObject::connect(object, &QObject::destroyed, object, []{ GeneralWorkerThread::instance()->workerDestroyed(); }); // Notify of destruction +} + +} +/*! @endcond */ diff --git a/lib/core/src/__private/qx-generalworkerthread.h b/lib/core/src/__private/qx-generalworkerthread.h new file mode 100644 index 00000000..32559499 --- /dev/null +++ b/lib/core/src/__private/qx-generalworkerthread.h @@ -0,0 +1,53 @@ +#ifndef QX_GENERALWORKERTHREAD_H +#define QX_GENERALWORKERTHREAD_H + +// Qt Includes +#include + +// Intra-component Includes +#include "qx/core/qx-threadsafesingleton.h" + +/*! @cond */ +namespace Qx +{ + +/* A dedicated thread for Qx worker objects so that we can be sure the thread they run on is never blocked + * for long periods. Automatically starts up when objects are added, and stops when the last one is removed. + * + * Although this is called "GeneralWorkerThread", it's more so its manager. The real thread is created + * by QThread. So, this class can be called anywhere, at anytime; therefore, we make it TSS + */ +class GeneralWorkerThread : public ThreadSafeSingleton +{ + QX_THREAD_SAFE_SINGLETON(GeneralWorkerThread); +//-Instance Variables------------------------------------------------------------------------------------------------- +private: + QThread mThread; + uint mWorkerCount; + +//-Constructor------------------------------------------------------------------------------------------------- +private: + GeneralWorkerThread(); + +//-Destructor------------------------------------------------------------------------------------------------- +public: + ~GeneralWorkerThread(); + +//-Class Functions--------------------------------------------------------------------------------------------- +public: + + +//-Instance Functions-------------------------------------------------------------------------------------------- +private: + void startThread(); + void stopThread(bool wait = false); + void workerDestroyed(); + +public: + void moveTo(QObject* object); +}; + +} +/*! @endcond */ + +#endif // QX_GENERALWORKERTHREAD_H