16.3 Asynchronous in {shiny}
R is single threaded but the world isn’t
- B and C depend on A
- D depends on B and C
Synchronous code:
- either A -> B -> C -> D
- or A -> C -> B -> D
If B runs on another computer and takes a long time
Synchronous version:
A -> start-B retrieve-B -> C -> D
\ / (My-server)
\ / ---------------
- run-B - (Remote-server)
Asynchronous version:
A -> start-B -> C -> retrieve-B -> D
\ / (My-server)
\ / ---------------
- run B ---- (Remote-server)
Packages:
- {future}: send computation elsewhere
- {promises}: objects for handling async computation
16.3.1 Asynchronously ease cross-session issues
- One R/shiny session
- Multiple users
- A few, specific, long-running operations
“Promise” = A stand-in for the eventual result of an operation
- https://rstudio.github.io/promises/articles/intro.html
- %...>%
- Promise-compatible pipe
- What happens on success
- %...!%
- What happens on failure
library(shiny)
library(future)
library(promises)
plan(multisession)
function(){
ui <-tagList(
# This will receive the output of the future
verbatimTextOutput("rnorm")
)
}
function(
server <-
input,
output,
session
){$rnorm <- renderPrint({
output# Sending the rnorm to be run in another session
future({
Sys.sleep(20)
return(rnorm(5))
%...>%
}) print(.) %...!%
stop(.)
})
}
shinyApp(ui, server) s <-
runApp(s, launch.browser = FALSE)
Plan:
- run the above
- launch the app in two separate browser windows (X and Y)
- wait 20 seconds
- If one session blocked the other:
- X results would show at ~ 20secs
- Y results at ~ 40secs
- If there is no cross-session blocking
- Both X and Y results would show at ~ 20secs
16.3.2 Asynchronously ease within-session issues
Compare this (which blocks):
...$rnorm <- renderPrint({
output# Sending the rnorm to be run in another session
# At this point, {shiny} is waiting for the future
# to be solved before doing anything else
future({
Sys.sleep(3)
return(rnorm(5))
%...>%
}) print(.) %...!%
stop(.)
}) ...
with this (which doesn’t block):
reactiveValues(
rv <-output = NULL
)
future({
Sys.sleep(5)
rnorm(5)
%...>%
}) # When the future is resolved, we assign the
# output to rv$output
(function(result){
$output <- result
rv%...!%
}) # If ever the future outputs an error, we switch
# back to NULL for rv$output, and throw a warning
# with the error
(function(error){
$output <- NULL
rvwarning(error)
})
# output$rnorm will be printed whenever rv$output
# is available (i.e. after around 5 seconds)
$rnorm <- renderPrint({
outputreq(rv$output)
})