reactive.extended_task

reactive.extended_task(func=None)

Decorator to mark an async function as a slow computation. This will cause the function to be run in a background asyncio task, and the results will be available via the ExtendedTask object returned by the decorator.

Unlike normal async render functions, effects, and calcs, extended_task async computations do not block Shiny reactive processing from proceeding. This means that they can be used to perform long-running tasks without freezing the session that owns them, nor other sessions.

However, this also means that they cannot access reactive sources. This is because processing of inputs and reactivity is not blocked, and so the reactive sources may change while the computation is running, which is almost never the desired behavior. If any reactive sources are needed by the computation, the decorated function must take them as parameters, and the resulting ExtendedTask object must be invoked with the corresponding arguments.

Parameters

func: Optional[Callable[P, Awaitable[R]]] = None

The function to decorate. It must be async. It can take any parameters and return any value (including None).

Returns

Type Description
ExtendedTask[P, R] | Callable[[Callable[P, Awaitable[R]]], ExtendedTask[P, R]] An ExtendedTask object that can be used to check the status of the computation and retrieve the result.

Examples

#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400

## file: app.py
import asyncio
from datetime import datetime

from shiny import reactive, render
from shiny.express import input, ui

ui.h5("Current time")


@render.text
def current_time():
    reactive.invalidate_later(1)
    return datetime.now().strftime("%H:%M:%S")


with ui.p():
    "Notice that the time above updates every second, even if you click the button below."


@ui.bind_task_button(button_id="btn")
@reactive.extended_task
async def slow_compute(a: int, b: int) -> int:
    await asyncio.sleep(3)
    return a + b


with ui.layout_sidebar():
    with ui.sidebar():
        ui.input_numeric("x", "x", 1)
        ui.input_numeric("y", "y", 2)
        ui.input_task_button("btn", "Compute, slowly")
        ui.input_action_button("btn_cancel", "Cancel")

    @reactive.effect
    @reactive.event(input.btn, ignore_none=False)
    def handle_click():
        # slow_compute.cancel()
        slow_compute(input.x(), input.y())

    @reactive.effect
    @reactive.event(input.btn_cancel)
    def handle_cancel():
        slow_compute.cancel()

    ui.h5("Sum of x and y")

    @render.text
    def show_result():
        return str(slow_compute.result())