Shiny Promises Future Is Not Working on Eventreactive

Shiny promises future is not working on eventReactive

Welcome to SO!

This thread discusses the same issue.

Please also see the detailed answer from Joe Cheng on GitHub.

The main problem you are experiencing is reflected by his following statement:

The goal, at least for this release of Shiny, is not to allow this
kind of intra-session responsiveness, but rather, inter-session; i.e.,
running an async operation won't make its owning session more
responsive, but rather will allow other sessions to be more
responsive.

However, there are ways to work around this behaviour by running the future in a background R process with e.g. library(callr) or more convenient library(future.callr) and it's plan(callr).

Here is a working version of your code:

library(future)
library(promises)
library(future.callr)
plan(callr)

heavyFunction <- function(n) {
Sys.sleep(n)
print(n)
}

ui <- fluidPage(
br(),
actionButton("go", "Show the data"),
br(), br(),
textOutput("result0sec"),
textOutput("result10sec")
)

server <- function(input, output, session) {
futureData <- reactiveValues(data10 = NULL)

data0 <- eventReactive(input$go, {
heavyFunction(0)
})

observeEvent(input$go, {
myFuture <- future({
heavyFunction(5)
})

then(
myFuture,
onFulfilled = function(value) {
futureData$data10 <<- value
},
onRejected = NULL
)
return(NULL)
})

output$result0sec <- renderText({
data0()
})

output$result10sec <- renderText({
req(futureData$data10)
})
}

shinyApp(ui, server)

The most important point here is to realize, that you shouldn't return your future directly, otherwise it will block all other actions - the observer returns nothing, it only has the side-effect of triggering the callback-function.

R Shiny promise/future blocks process

Try this:

library(data.table)
library(ggplot2)
library(promises)
library(future)

plan(multiprocess)

server <- function(input, output, session) {

dt_trend <- eventReactive(
input$run_trends,
{
dat_func <- function() {

start_time <- Sys.time()
dt <- data.table(x = rnorm(100), y = rnorm(100))
trendy_tbl <- head(dt, 10)
ggplo1 <- ggplot(dt) + geom_point(aes(x=x,y=y))
Sys.sleep(10)
list(
trendy_tbl
, ggplo1
, paste0('time: ', round(Sys.time() - start_time), ' сек.')
)
}

# Returning future
future({
dat_func()
})
})

output$trend_tbl <- renderDT({dt_trend()[[1]]})
output$trend_plotly <- renderPlot({dt_trend()[[2]]})
output$trends_time <- renderText({dt_trend()[[3]]})

}

The key ideas are:

  • Make sure you use shiny version > 1.1.0 (April 2018) which introduced async support.
  • do NOT use future::value as it blocks and waits for the future, precisely what we want to avoid.
  • instead, return the future in a reactive. In this case, this means using eventReactive instead of observeEvent.
  • Access the value of the future via the reactive. Note that your reactive value is now a future! This means you need to use future handlers to use the value. (*)
  • You can also return a future in any renderXXX function. Useful for example if you have large time-consuming plots.

(*) In practice, this means you need to do

renderDT(dt_trend() %>% then(~.[[1]]))
# or
renderDT(dt_trend() %>...% `[[`(1))

where then is from the promises package and [[ is the subsetting function from base R (x[[i]] is actually semantic sugar for`[[`(x, i) !).

In your example, you basically compute everything in a single future in dt_trend. You may want to consider using multiple small futures instead. You can load the data in a reactive with a future, then keep the output code in your renderXXX functions, wrapped in a future if needed.

There is a good vignette on using promises with shiny available by running vignette("shiny", package = "promises")(**). It is also available online on cran or on the rstudio blog.

(**) If you installed promises with install_github("rstudio/promises"), you most likely have to re-install with build_vignettes = TRUE first.

How to make an operation uninterruptible in R shiny

Thanks @Shree for pointing out the solution. After reading the response from Joe Cheng. It seems like the key is to:

Hide the async operation from Shiny by not having the promise be the last expression.

The problem is resolved by creating a reactive value and assign the promise to it in observeEvent as the side effect.

server <- function(input, output, session) {

output$time <- renderText({
invalidateLater(1000)
as.character(Sys.time())
})

process <- reactiveVal()

observeEvent(input$button,{
output$isbusy <- renderText("busy") # a simple busy indicator
future({
Sys.sleep(5)
runif(1)
}) %...>%
process()
# Hide the async operation from Shiny by not having the promise be the last expression
NULL # important
})

output$result <- renderText({
output$isbusy <- renderText("") # a simple busy indicator
process()
})
}

why does future::future() not work with dbAppendTable in shiny?

The DBI package and all their implementations (like RSQLite) do use non-exportable objects (basically memory pointers), see:

https://cran.r-project.org/web/packages/future/vignettes/future-4-non-exportable-objects.html

You can see an error message when you configure futures like this:

options(future.globals.onReference = "error")
# ... your code goes here

# Error in FALSE :
# Detected a non-exportable reference (‘externalptr’) in one of the globals (‘con’ of class ‘SQLiteConnection’) used in the future expression
# Timing stopped at: 0.028 0 0.028


Related Topics



Leave a reply



Submit