From eba8431a0b8b11b68ab643f19625f08408872876 Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Mon, 3 Aug 2026 23:45:42 -0500 Subject: [PATCH] API nit: Use Util::AsyncOnObject everywhere instead of QTimer::singleShot This is just to make the API easier to follow. Also cleaned up AsyncOnObject to not default to caarse timer unless the interval is >2000 msec. Also we use QMetaObject::invokeMethod() directly by default if the AsyncOnObjectCall() has when_ms <= 0 (default). This should have no real observable change on app behavior other than being a code cleanup & nit. --- src/App.cpp | 2 +- src/Controller.h | 2 +- src/Logger.cpp | 3 ++- src/Mixins.h | 2 +- src/RPC.cpp | 4 ++-- src/Storage.cpp | 2 +- src/SubsMgr.cpp | 2 +- src/Util.cpp | 2 +- src/Util.h | 27 +++++++++++++++++---------- 9 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/App.cpp b/src/App.cpp index 9de1a8f..c84b7a9 100644 --- a/src/App.cpp +++ b/src/App.cpp @@ -100,7 +100,7 @@ App::App(int argc, char *argv[]) Log() << "Shutdown requested" << (signalled ? " via signal" : ""); this->quit(); }, Qt::QueuedConnection); - QTimer::singleShot(0, this, &App::startup); // register to run after app event loop start + Util::AsyncOnObject(this, [this]{App::startup();}); // register to run after app event loop start } App::~App() diff --git a/src/Controller.h b/src/Controller.h index cd9466b..2cfab96 100644 --- a/src/Controller.h +++ b/src/Controller.h @@ -161,7 +161,7 @@ protected slots: private: friend class CtlTask; /// \brief newTask - Create a specific task using this template factory function. The task will be auto-started the - /// next time this thread enters the event loop, via a QTimer::singleShot(0,...). + /// next time this thread enters the event loop, via a Util::AsyncOnObject() call /// /// \param connectErroredSignal If true, auto-connect signal CtlTask::errored() to this->genericTaskErrored() /// \param args The rest of the args get passed to the c'tor of the concrete class specified (in the template arg). diff --git a/src/Logger.cpp b/src/Logger.cpp index e8bc732..e9e8098 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -18,6 +18,7 @@ // #include "Common.h" #include "Logger.h" +#include "Util.h" #include #include @@ -41,7 +42,7 @@ namespace { { if (level == Logger::Fatal) { if (qApp && !QCoreApplication::startingUp()) - QTimer::singleShot(0, qApp, []{qApp->exit(1);}); + Util::AsyncOnObject(qApp, []{qApp->exit(1);}); else std::exit(1); } diff --git a/src/Mixins.h b/src/Mixins.h index 1a406b0..26b7559 100644 --- a/src/Mixins.h +++ b/src/Mixins.h @@ -220,7 +220,7 @@ public: virtual ~ProcessAgainMixin() override; protected: virtual void process() = 0; - void AGAIN(int when_ms=0) { QTimer::singleShot(qMax(0, when_ms), qobj(), [this]{process();}); } + void AGAIN(int when_ms=0) { Util::AsyncOnObject(qobj(), [this]{ process(); }, when_ms); } }; Q_DECLARE_METATYPE(IdMixin::Id); diff --git a/src/RPC.cpp b/src/RPC.cpp index 8f1d08f..50f9193 100644 --- a/src/RPC.cpp +++ b/src/RPC.cpp @@ -770,7 +770,7 @@ namespace RPC { readPaused = b; if (!readPaused && hadSkips) // we had some skipped on_readyReads() -- resume - QTimer::singleShot(0, this, [this]{on_readyRead();} ); + Util::AsyncOnObject(this, [this]{on_readyRead();} ); emit readPausedStateChanged(readPaused); } @@ -1034,7 +1034,7 @@ namespace RPC { if (auto avail = socket->bytesAvailable(); avail > 0 && (MAX_BUFFER <= 0 || avail <= MAX_BUFFER)) { // callback is on socket as receiver this way if socket dies and is deleted, callback never happens. // *taps forehead* - QTimer::singleShot(0, socket, [this]{on_readyRead();}); + Util::AsyncOnObject(socket, [this]{on_readyRead();}); } } if (MAX_BUFFER > 0 && socket->bytesAvailable() > MAX_BUFFER) [[unlikely]] { diff --git a/src/Storage.cpp b/src/Storage.cpp index 6d20ef9..12c4f74 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -2583,7 +2583,7 @@ void Storage::save(SaveSpec typed_spec) // atomic variable is not 0). if (const auto spec = IntType(typed_spec); ! p->pendingSaves.fetch_or(spec)) { - QTimer::singleShot(0, this, [this]{save_impl();}); + Util::AsyncOnObject(this, [this]{save_impl();}); } } diff --git a/src/SubsMgr.cpp b/src/SubsMgr.cpp index 7292e3e..af5115e 100644 --- a/src/SubsMgr.cpp +++ b/src/SubsMgr.cpp @@ -109,7 +109,7 @@ void SubsMgr::on_started() }, Qt::QueuedConnection); conns += connect(this, &SubsMgr::requestRemoveZombiesSoon, this, [this](int when_ms) { // remove zombies in when_ms, outside normal rate-limiting timer - QTimer::singleShot(std::max(when_ms, 0), this, [this]{ removeZombies(true /* forced */); }); + Util::AsyncOnObject(this, [this]{ removeZombies(true /* forced */); }, when_ms); }, Qt::QueuedConnection); callOnTimerSoon(kRemoveZombiesTimerIntervalMS, kRemoveZombiesTimerName, [this]{ removeZombies(false); return true;}, true); } diff --git a/src/Util.cpp b/src/Util.cpp index 4f5dd69..60f9c68 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -780,7 +780,7 @@ Log::~Log() } // Fatal should signal a quit even here if (level == Logger::Level::Fatal && qApp) { - QTimer::singleShot(0, qApp, []{ qApp->quit(); }); + Util::AsyncOnObject(qApp, []{ qApp->quit(); }); } } } diff --git a/src/Util.h b/src/Util.h index 9467dde..938a3e0 100644 --- a/src/Util.h +++ b/src/Util.h @@ -651,6 +651,21 @@ namespace Util { /// - the string has an even number of characters (or is empty) bool IsValidHex(const QByteArray &maybeHexStr); + /// Convenience for just setting up a QTimer::singleShot and/or QMetaObject::invokeMethod on an object, calling a + /// lambda as the timeout. + /// By default if when_ms is <= 0, the object will have the lambda invoked in its thread as soon as it + /// returns to the event loop. Always returns immediately. + template + void AsyncOnObject(const QObject *obj_, const VoidFuncT & lambda, int when_ms=0, std::optional ttype = std::nullopt) { + QObject * const obj = const_cast(obj_); // hacky but it is what it is + if (when_ms <= 0) // special case for when_ms is <=0, use invoke instead (avoids allocating a timer) + QMetaObject::invokeMethod(obj, lambda, Qt::ConnectionType::QueuedConnection); + else { + if (!ttype) ttype = when_ms <= 2000 ? Qt::TimerType::PreciseTimer : Qt::TimerType::CoarseTimer; + QTimer::singleShot(when_ms, *ttype, obj, lambda); + } + } + /// Call lambda() in the thread context of obj's thread. Will block until completed. /// If timeout_ms is not specified or negative, will block forever until lambda returns, /// otherwise will block for timeout_ms ms. Will throw TimeoutException if the timeout @@ -668,7 +683,7 @@ namespace Util { } else { auto taskp = std::make_shared< std::packaged_task >(lambda); auto future = taskp->get_future(); - QTimer::singleShot(0, const_cast(obj), [taskp] { (*taskp)(); }); + Util::AsyncOnObject(obj, [taskp] { (*taskp)(); }); if (timeout_ms >= 0) { if (auto status = future.wait_for(std::chrono::milliseconds(timeout_ms)); status != std::future_status::ready) { @@ -698,14 +713,6 @@ namespace Util { /// false otherwise. (Note lambda may still run later asynchronously). bool VoidFuncOnObjectNoThrow(const QObject *obj, const VoidFunc & lambda, int timeout_ms=-1); - /// Convenience for just setting up a QTimer::singleShot on an object, calling a lambda as the timeout. - /// By default if when_ms is 0, the object will have the lambda invoked in its thread as soon as it - /// returns to the event loop. Always returns immediately. - template - void AsyncOnObject(const QObject *obj, const VoidFuncT & lambda, unsigned when_ms=0, Qt::TimerType ttype = Qt::TimerType::CoarseTimer) { - QTimer::singleShot(int(when_ms), ttype, const_cast(obj), lambda); - } - /// This is an alternative to creating signal/slot pairs for calling a method on an object that runs in another /// thread. /// @@ -713,7 +720,7 @@ namespace Util { /// a private slot _myMethod()). /// /// To save typing, this template can just allow you to directly call a method on an object in its thread (uses - /// QTimer::singleShot). + /// QTimer::singleShot and/or QMetaObjecT::invokeMethod). /// /// Arguments are capture-copied. ///