ShinyConf 2024
2024-04-18
Ideally, all the code you put into your Shiny app should be fast and responsive.
If something is slow, first try making it fast.
From my talk from rstudio::conf 2019, “Shiny in Production: Principles, Practices, and Tools”.
renderCachedPlot
, bindCache
).But sometimes, things are just going to be slow…
Credit: This demo builds on examples by Veerle van Leemput at @hypebright/async_shiny
This time with ExtendedTask
server <- function(input, output, session) {
# Define the task
msg_task <- ExtendedTask$new(function(name) {
future({
# Simulate a long-running task
Sys.sleep(5)
paste0("Hello, ", name, "!")
})
})
observeEvent(input$go, {
# Start the task
msg_task$invoke(input$name)
})
output$message <- renderText({
# Use the task's result
msg_task$result()
})
}
# Define the task
@reactive.extended_task
async def msg_task(name):
# Simulate a long-running task
await asyncio.sleep(5)
return f"Hello, {name}!"
@reactive.effect
@reactive.event(input.go)
def _():
# Start the task
msg_task.invoke(input.name())
@render.text
def message():
# Use the task's result
return msg_task.result()
For Shiny for R:
https://shiny.posit.co/r/articles/improve/nonblocking/
For Shiny for Python:
In 2017-2018, we introduced async programming to R, and then to Shiny.
A very technically challenging and conceptually elegant feature.
Shiny Async, while necessary, was never a truly satisfying solution.
The graph is at equilibrium now.
Everything up til now has been a single “tick” (as in “tick of the clock”).
Somewhere in the depths of Shiny is a loop that looks like this:
while (TRUE) {
changes <- wait_for_input_changes()
changed_outputs <- recompute_all_affected_things(changes)
send_outputs(changed_outputs)
}
Each trip through the loop is a “reactive tick”.
Only at the beginning of a reactive tick do we check for input changes.
Only at the end of a reactive tick do we send the outputs to the client.
Long-running tasks bring the reactive graph to a halt
Long-running tasks bring the reactive graph to a halt
How can we separate the task from the tick?
With Shiny Async, you can run multiple graphs concurrently…
But you can’t run multiple tasks concurrently within a single graph because the shape of the graph is unchanged.
future is a great package, but it’s not the only way to run R code in the background.
Pros:
Cons:
{mirai} is a new package by Charlie Gao that, like future, can run code in a background R process.
Pros:
Cons:
{crew} by Will Landau builds on mirai to provide a convenient way to launch and manage multiple (potentially very many) tasks.
crew works with ExtendedTask and has examples in its docs. I’m not yet familiar enough with crew to give it a full evaluation.
A {future.mirai} package is currently in beta, and should combine the convenient API of future with the low overhead of mirai.
Thank you!
See these slides at https://rpubs.com/jcheng/beyond-async