morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
morph::qt::QtExecutor Class Reference

IExecutor implementation that posts tasks to a Qt event loop. More...

#include <qt_executor.hpp>

Inheritance diagram for morph::qt::QtExecutor:
morph::exec::IExecutor

Public Member Functions

 QtExecutor (QObject *context=QCoreApplication::instance())
 Constructs an executor that posts tasks to context's thread.
 QtExecutor (const QtExecutor &)=delete
QtExecutor & operator= (const QtExecutor &)=delete
 QtExecutor (QtExecutor &&)=delete
QtExecutor & operator= (QtExecutor &&)=delete
 ~QtExecutor () override=default
 Destroys the lifetime token, so any still-queued task becomes a no-op.
void post (std::function< void()> fn) override
 Posts fn to the Qt event loop for execution on the context's thread.
Public Member Functions inherited from morph::exec::IExecutor
 IExecutor (const IExecutor &)=delete
IExecutor & operator= (const IExecutor &)=delete
 IExecutor (IExecutor &&)=delete
IExecutor & operator= (IExecutor &&)=delete
virtual bool isSerial () const noexcept
 Whether this executor runs one task at a time, each after the tasks posted before it: a strand, a GUI loop, a pumped MainThreadExecutor.
::core::async::IExecutor & coreExecutor () noexcept
 This executor as a core-cpp executor: the identity an core::async::ExecutorScope names.

Detailed Description

IExecutor implementation that posts tasks to a Qt event loop.

Uses QMetaObject::invokeMethod with Qt::QueuedConnection so the callable always runs on the thread that owns the configured context object — by default QCoreApplication::instance(), i.e. the GUI thread.

Thread-safe: post() may be called from any thread. No QObject subclass is required by the caller.

Each delivered task runs inside a core::async::ExecutorScope naming this executor, so morph::exec::runningOn(*this) is true inside it.

A task still queued when this executor is destroyed is dropped, not run. post() enqueues and returns; it does not run the callable, and the queued Qt event outlives the post() call that created it. Joining the worker pool that made the call proves the post() happened — never that the event was delivered. Without a guard, an event still on the Qt queue at teardown is delivered against a freed executor and reads _context off freed memory.

Bridge::executeVia reaches that state on an ordinary run: it chains three Completion objects per dispatched action, each settled from inside the previous one's delivered callback, so a caller waiting only on its own top-level completion can observe "done" while an intermediate post is still queued. When that stale event is pumped, its body calls post() for the next link in the chain — against a freed executor. It segfaults on an uninstrumented build, not only under a sanitizer.

Each queued task therefore carries a weak observer of this executor's lifetime and does nothing if the executor is already gone. Dropping is the correct outcome rather than a lesser evil: a chain being torn down has nobody left to observe its result, and Completion's orphan logging already covers a completion that never resolves.

Boundary of the guarantee. The check assumes this executor is destroyed on the same thread that runs its context's event loop, which holds for every owner in this repository. Destroying one from another thread while its loop is mid-delivery still needs external synchronisation: the token covers teardown with events still queued, not a genuine cross-thread race.

Definition at line 53 of file qt_executor.hpp.

Constructor & Destructor Documentation

◆ QtExecutor()

morph::qt::QtExecutor::QtExecutor ( QObject * context = QCoreApplication::instance())
inlineexplicit

Constructs an executor that posts tasks to context's thread.

Parameters
contextQObject whose owning thread's event loop runs posted tasks; QMetaObject::invokeMethod dispatches to whichever thread context->thread() reports at the time each task is posted, so tasks posted after context is moved to a different thread run there. Defaults to QCoreApplication::instance(), i.e. the GUI thread. Passing nullptr (e.g. when constructed before QCoreApplication exists) makes post() a no-op, matching QMetaObject::invokeMethod's own handling of a null target. Borrowed, not owned: a non-null context must outlive this executor.

Definition at line 66 of file qt_executor.hpp.

Member Function Documentation

◆ post()

void morph::qt::QtExecutor::post ( std::function< void()> fn)
inlineoverridevirtual

Posts fn to the Qt event loop for execution on the context's thread.

Returns immediately. fn is invoked asynchronously once the event loop processes the queued event.

Runs only if this executor is still alive when the event is delivered; see the class docstring.

Parameters
fnCallable to execute on the context object's thread.

Implements morph::exec::IExecutor.

Definition at line 86 of file qt_executor.hpp.


The documentation for this class was generated from the following file: