spacr.qt.job_runner¶
Run a callable off the GUI thread and deliver its result on the GUI thread.
Screens such as DbBrowserScreen, PlateViewScreen and ReportScreen
previously each had their own copy of this logic, and the copies differed: two
connected thread.finished to a closure, which never retires the job (see
JobRunner._retire_finished_jobs). This module implements the rules once, for any
screen that moves work off the GUI thread.
The rules:
PipelineWorker.finishedis emitted on the worker thread. A closure connected to it is invoked there and must not touch widgets. Connecting the signal to a bound method of a GUI-thread QObject lets Qt queue the call onto the GUI thread; this runner uses_worker_finishedfor that delivery. A worker-thread slot can also relay aSignalto such a receiver without touching widgets itself.QThread.finishedmust be connected to a bound method of a GUI-thread QObject, never a closure. PySide6 makes the QThread itself the receiver for a closure, andspacr.qt.bridge.make_thread()connectsthread.finished -> thread.deleteLaterfirst. Slots run in connection order, so the DeferredDelete is posted ahead of the closure’s metacall and Qt discards queued events for a destroyed receiver: the job is never retired andactive_jobs()never returns to zero.A strong reference to both the QThread and the worker must be held until
thread.finished. A QThread garbage-collected while running aborts the process.Result delivery and thread retirement are separate.
JobRunner.cancel()clears pending deliveries immediately; a late completion then emits nojob_finishedsignal. The finished-thread sweep still releases the thread references independently.
Threaded submissions go through make_thread, so they appear in the
process-wide spacr.qt.bridge.registry() and turn the background
activity spinner (spacr.qt.widgets.activity_spinner). Inline mode
bypasses this registration and does not change the runner’s busy state.
Classes¶
Run callables off the GUI thread; deliver their results on it. |
Functions¶
|
Cancel live runners; return how many shutdown calls completed normally. |
Module Contents¶
- class spacr.qt.job_runner.JobRunner(parent: PySide6.QtCore.QObject | None = None, *, threaded: bool = True, app_key: str = '', user_visible: bool = True)[source]¶
Bases:
PySide6.QtCore.QObjectRun callables off the GUI thread; deliver their results on it.
- Parameters:
parent – the widget that owns the work. Kept as the runner’s Qt parent so the runner dies with it.
threaded –
Falseruns work and its handler inline on the calling thread. Failure and completion signals are emitted before return; no worker is registered and busy state does not change.app_key – the name jobs appear under in the run registry, and so in the activity spinner’s tooltip.
user_visible –
Falsefor housekeeping the user did not start. Such a job still turns the activity spinner – something IS running – but never claims a run banner. The usage poller submits every two seconds; without this Home flashes “<module> usage - running” on and off for as long as a module screen is open.
Create a runner for one widget’s background work.
It registers itself the moment it exists, so a runner cannot be created and then missed by the quit-time drain; the registration is weak, so it holds nothing alive that Qt would otherwise collect.
- Parameters:
parent – the owning object, or
None.threaded – run jobs on a worker thread.
Falseruns work and its handler on the calling thread without registering a worker or changing busy state.app_key – how this runner’s work is named in the run registry.
user_visible – whether these jobs count as runs the user started. Set
Falsefor housekeeping, so Home’s run banner does not show it as a run.
- active_jobs() int[source]¶
Count tracked worker references, including running or not-yet-retired jobs.
Finished threads remain counted until the retirement sweep. Shutdown clears this count even when a thread has been parked elsewhere.
- cancel() None[source]¶
Abandon the results of everything in flight.
Request thread interruption without waiting, then clear pending deliveries and busy state immediately. Late completions are ignored and emit no
job_finishedsignal. Worker references remain until retirement or shutdown; interruption does not forcibly stop work. A worker’s error signal can still emitjob_failed.
- is_busy() bool[source]¶
Whether threaded completions are pending; cancel clears this immediately.
Inline work does not set this flag. A cancelled worker may still be running after this returns
False.
- pending_jobs() int[source]¶
Count pending threaded completion records, cleared immediately by cancel.
- shutdown(timeout_ms: int = 3000) None[source]¶
Cancel deliveries, then wait within a shared budget or park live threads.
Call from
closeEvent. Qt aborts the process if a running QThread is destroyed, so a bounded wait is needed when a screen closes mid-load.timeout_msis shared by every thread of this runner. Threads that outlast it are parked byspacr.qt.bridge.drain_thread()rather than terminated.
- submit(fn: Callable[[], Any], on_done: Callable[[Any], None] | None = None) bool[source]¶
Submit
fn()and deliver its successful result toon_done.- Parameters:
fn – a zero-argument callable. In threaded mode it runs off the GUI thread and must not touch widgets. Inline mode runs it on the calling thread.
on_done – optional handler for
fn’s return value, called on the GUI thread in threaded mode or inline otherwise. Cancellation discards pending threaded deliveries.
- Returns:
Trueafter starting a threaded job, before its outcome is known. Inline mode returnsFalseif either callable raises an exception; otherwise it returnsTrue.
- spacr.qt.job_runner.shutdown_all(timeout_ms: int = SHUTDOWN_BUDGET_MS) int[source]¶
Cancel live runners; return how many shutdown calls completed normally.
Call before Qt starts destroying widgets. Cancellation is requested for all runners before any wait, then they share one
timeout_mswait deadline. This bounds thread waiting, not the total application exit time. Threads that outlast the budget are parked byspacr.qt.bridge.drain_thread(), not terminated. Failed shutdown calls are logged, excluded from the return count, and do not stop the remaining runners from being processed.- Parameters:
timeout_ms – the total wait budget across every runner.