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.
This commit is contained in:
Calin Culianu 2026-08-03 23:45:42 -05:00
parent 92fd99fee8
commit eba8431a0b
No known key found for this signature in database
GPG key ID: 21810A542031C02C
9 changed files with 27 additions and 19 deletions

View file

@ -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()

View file

@ -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).

View file

@ -18,6 +18,7 @@
//
#include "Common.h"
#include "Logger.h"
#include "Util.h"
#include <QCoreApplication>
#include <QTimer>
@ -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);
}

View file

@ -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);

View file

@ -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]] {

View file

@ -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();});
}
}

View file

@ -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);
}

View file

@ -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(); });
}
}
}

View file

@ -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 <std::invocable VoidFuncT>
void AsyncOnObject(const QObject *obj_, const VoidFuncT & lambda, int when_ms=0, std::optional<Qt::TimerType> ttype = std::nullopt) {
QObject * const obj = const_cast<QObject *>(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<RET()> >(lambda);
auto future = taskp->get_future();
QTimer::singleShot(0, const_cast<QObject *>(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 <std::invocable VoidFuncT>
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<QObject *>(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.
///