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 App, reactive, render, ui

app_ui = ui.page_fixed(
    ui.h5("Current time"),
    ui.output_text("current_time"),
    ui.p(
        "Notice that the time above updates every second, even if you click the button below."
    ),
    ui.layout_sidebar(
        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"),
        ),
        ui.h5("Sum of x and y"),
        ui.output_text("show_result"),
    ),
)


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

    @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

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

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

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


app = App(app_ui, server)