edifice.use_async#
- edifice.use_async(fn_coroutine, dependencies=())[source]#
Asynchronous side-effect Hook inside a
@component
function.- Parameters:
fn_coroutine (
Callable
[[],Coroutine
[None
,None
,None
]]) – Function of no arguments which returns an async Coroutine to be run as a Task.dependencies (
Any
) – Thefn_coroutine
Task will be started when thedependencies
are not__eq__
to the olddependencies
.
- Return type:
Callable
[[],None
]- Returns:
A function which can be called to cancel the
fn_coroutine
Task manually.
Will create a new Task with the
fn_coroutine
coroutine.The
fn_coroutine
will be called every time thedependencies
change. Only onefn_coroutine
will be allowed to run at a time.Exceptions raised from the
fn_coroutine
will be suppressed.For general advice about
async
programming in Python see Developing with asyncio.use_async to fetch from the network#@component def WordDefinition(self, word:str): definition, definition_set = use_state("") async def fetcher(): try: definition_set("Fetch definition pending") x = await fetch_definition_from_the_internet(word) definition_set(x) except asyncio.CancelledError: definition_set("Fetch definition cancelled") raise except Exception: defintion_set("Fetch definition failed") cancel_fetcher = use_async(fetcher, word) with VBoxView(): Label(text=word) Label(text=definition) Button(text="Cancel fetch", on_click=lambda _:cancel_fetcher())
Cancellation#
Edifice will call cancel() on the async
fn_coroutine
Task in three situations:If the
dependencies
change before thefn_coroutine
Task completes, then thefn_coroutine
Task will be cancelled. Then the newfn_coroutine
Task will be started after the oldfn_coroutine
Task completes.The
use_async
Hook returns a function which can be called to cancel thefn_coroutine
Task manually. In the example above, thecancel_fetcher()
function can be called to cancel the fetcher.If this
@component
is unmounted before thefn_coroutine
Task completes, then thefn_coroutine
Task will be cancelled.
Write your async
fn_coroutine
function in such a way that it cleans itself up after exceptions. If you catch a CancelledError infn_coroutine
then always re-raise it.You may call a
use_state()
setter during aCancelledError
exception. If thefn_coroutine
Task was cancelled because the component is being unmounted, then theuse_state()
setter will have no effect.See also Task Cancellation.
Timers#
The
use_async
Hook is useful for timers and animation.Here is an example busy-wait UI indicator which is a bit more visually subtle than Qt’s barbershop-pole
QProgressBar
withminimum=0, maximum=0
.BusyWaitIndicator component#@ed.component def BusyWaitIndicator( self, visible: bool = True, size: int | None = None, color: QColor | None = None, ): """ Animated busy wait indicator which looks like ⬤⬤⬤⬤⬤ If not visible, will still occupy the same layout space but will be transparent and animation will not run. """ color_: QColor color_ = color if color else QApplication.palette().color(QPalette.ColorRole.Text) tick, tick_set = ed.use_state(0) async def animation(): if visible: while True: await asyncio.sleep(0.2) tick_set(lambda t: (t + 1) % 5) ed.use_async(animation, visible) with ed.HBoxView( size_policy=QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed), ): for i in range(0, 5): ed.Label( text="⬤", style={ "color": QColor( color_.red(), color_.green(), color_.blue(), 40 + (((i - tick) % 5) * 20) if visible else 0, ), } | {"font-size": size} if size else {}, )
Worker Process#
We can run an
async def my_subprocess
function in a worker Process by usingrun_subprocess_with_callback()
.run_subprocess_with_callback()
is good for spawing a parallel worker Process from a@component
because if the@component
is unmounted, thenrun_subprocess_with_callback()
will be cancelled and the Process will be immediately terminated. Which is usually what we want.use_async with run_subprocess_with_callback#async def my_subprocess(callback: Callable[[str], None]) -> int: # This function will run in a new Process in a new event loop. callback("Starting long calculation") await asyncio.sleep(100.0) x = 1 + 2 callback(f"Finished long calculation")) return x @component def LongCalculator(self): calculation_progress, calculation_progress_set = ed.use_state("") def my_callback(progress: str) -> None: # This function will run in the main process event loop. calculation_progress_set(progress) async def run_my_subprocess() -> None: try: x = await run_subprocess_with_callback(my_subprocess, my_callback) calculation_progress_set(f"Result: {x}") except asyncio.CancelledError: raise except Exception as e: calculation_progress_set(f"Error: {str(e)}") use_async(run_my_subprocess) Label(text=calculation_progress)