diff --git a/src/blackmisc/loghandler.cpp b/src/blackmisc/loghandler.cpp index 579ea3bf3..6f6847581 100644 --- a/src/blackmisc/loghandler.cpp +++ b/src/blackmisc/loghandler.cpp @@ -9,6 +9,7 @@ #include "loghandler.h" #include "algorithm.h" +#include "worker.h" #include #include @@ -139,7 +140,13 @@ namespace BlackMisc void CLogSubscriber::changeSubscription(const CLogPattern &pattern) { - Q_ASSERT(CLogHandler::instance()->thread() == QThread::currentThread()); + if (CLogHandler::instance()->thread() != QThread::currentThread()) + { + Q_ASSERT(thread() == QThread::currentThread()); + singleShot(0, CLogHandler::instance()->thread(), this, [ = ]() { changeSubscription(pattern); }); + return; + } + unsubscribe(); m_handler = CLogHandler::instance()->handlerForPattern(pattern); @@ -147,12 +154,18 @@ namespace BlackMisc { m_handler->enableConsoleOutput(m_enableFallThrough); } - connect(m_handler.data(), &CLogPatternHandler::messageLogged, this, &CLogSubscriber::ps_logMessage); + connect(m_handler.data(), &CLogPatternHandler::messageLogged, this, &CLogSubscriber::ps_logMessage, Qt::DirectConnection); } void CLogSubscriber::unsubscribe() { - Q_ASSERT(CLogHandler::instance()->thread() == QThread::currentThread()); + if (CLogHandler::instance()->thread() != QThread::currentThread()) + { + Q_ASSERT(thread() == QThread::currentThread()); + singleShot(0, CLogHandler::instance()->thread(), this, [ = ]() { unsubscribe(); }); + return; + } + if (m_handler) { m_handler->disconnect(this); @@ -161,7 +174,13 @@ namespace BlackMisc void CLogSubscriber::inheritConsoleOutput() { - Q_ASSERT(CLogHandler::instance()->thread() == QThread::currentThread()); + if (CLogHandler::instance()->thread() != QThread::currentThread()) + { + Q_ASSERT(thread() == QThread::currentThread()); + singleShot(0, CLogHandler::instance()->thread(), this, [ = ]() { inheritConsoleOutput(); }); + return; + } + m_inheritFallThrough = true; if (m_handler) { @@ -171,7 +190,13 @@ namespace BlackMisc void CLogSubscriber::enableConsoleOutput(bool enable) { - Q_ASSERT(CLogHandler::instance()->thread() == QThread::currentThread()); + if (CLogHandler::instance()->thread() != QThread::currentThread()) + { + Q_ASSERT(thread() == QThread::currentThread()); + singleShot(0, CLogHandler::instance()->thread(), this, [ = ]() { enableConsoleOutput(enable); }); + return; + } + m_inheritFallThrough = false; m_enableFallThrough = enable; if (m_handler) diff --git a/src/blackmisc/loghandler.h b/src/blackmisc/loghandler.h index 41b6c2797..16d2fc83b 100644 --- a/src/blackmisc/loghandler.h +++ b/src/blackmisc/loghandler.h @@ -175,30 +175,38 @@ namespace BlackMisc /*! * A helper class for subscribing to log messages matching a particular pattern, with the ability to * change the pattern at runtime. + * + * Also provides a thread-safe API for interacting with the CLogHandler. */ class CLogSubscriber : public QObject { Q_OBJECT public: + //! Default constructor, for when you're not interested in messages and just want to control the console output. + CLogSubscriber(QObject *parent = nullptr) : QObject(parent) {} + //! Construct a subscriber which forwards messages to the given slot of parent. template CLogSubscriber(T *parent, F slot) : QObject(parent) { - Q_ASSERT(CLogHandler::instance()->thread() == QThread::currentThread()); QObject::connect(this, &CLogSubscriber::ps_messageLogged, parent, slot); } //! Change the pattern which you want to subscribe to. + //! \threadsafe If not called from the main thread, it will run asynchronously. void changeSubscription(const CLogPattern &pattern); //! Unsubscribe from all messages. + //! \threadsafe If not called from the main thread, it will run asynchronously. void unsubscribe(); //! \copydoc CLogPatternHandler::enableConsoleOutput + //! \threadsafe If not called from the main thread, it will run asynchronously. void enableConsoleOutput(bool enable); //! \copydoc CLogPatternHandler::inheritConsoleOutput + //! \threadsafe If not called from the main thread, it will run asynchronously. void inheritConsoleOutput(); signals: diff --git a/src/blackmisc/worker.h b/src/blackmisc/worker.h index ee1b92931..d25e2855a 100644 --- a/src/blackmisc/worker.h +++ b/src/blackmisc/worker.h @@ -15,18 +15,36 @@ #include #include #include +#include +#include #include #include namespace BlackMisc { + //! \private Class for synchronizing singleShot() task with its owner. + class CSingleShotController : public QObject + { + Q_OBJECT + public: + CSingleShotController(QObject *parent) : QObject(parent), m_strongRef(QSharedPointer::create(0)) {} + ~CSingleShotController() { auto wr = weakRef(); m_strongRef.clear(); waitForNull(wr); } + QWeakPointer weakRef() const { return m_strongRef.toWeakRef(); } + private: + static void waitForNull(QWeakPointer wp) { while (wp) { QThread::msleep(10); } } + QSharedPointer m_strongRef; // pointee type doesn't matter, we only care about the reference count + }; + /*! * Starts a single-shot timer which will run in an existing thread and call a task when it times out. * * Useful when a worker thread wants to push small sub-tasks back to the thread which spawned it. - * \see QTimer::singleShot() + * + * If an owner pointer is specified, then the task may be cancelled if the owner is deleted, but the + * owner will not be deleted while the task is running (its destructor will wait for the task to end). */ + //! @{ template void singleShot(int msec, QThread *target, F task) { @@ -40,6 +58,18 @@ namespace BlackMisc }); QMetaObject::invokeMethod(timer, "start", Q_ARG(int, msec)); } + template + void singleShot(int msec, QThread *target, QObject *owner, F task) + { + Q_ASSERT(QThread::currentThread() == owner->thread()); + auto weakRef = (new CSingleShotController(owner))->weakRef(); + singleShot(msec, target, [ = ]() + { + auto strongRef = weakRef.toStrongRef(); + if (strongRef) { task(); } + }); + } + //! @} /*! * Just a subclass of QThread whose destructor waits for the thread to finish.