Call brilliant JavaScript callback from the future

In brilliant state, you can call client-side callbacks written in javascript from server logic. Let's say in ui.R you have JavaScript, including a function called setText :

 tags$script(' Shiny.addCustomMessageHandler("setText", function(text) { document.getElementById("output").innerHTML = text; }) ') 

then in server.R you can call session$sendCustomMessage(type='foo', 'foo') .

Suppose I have a long-term function that returns some data to build. If I do this normally, thread R is busy during the execution of this function and therefore cannot process additional requests during this time. It would be very useful to be able to run this function using a futures package so that it runs asynchronously with the code and calls the callback asynchronously. However, when I tried, it just didn't work.

Sorry if this is not very clear. As a simple example, you need to follow these steps until you uncomment the two lines trying to call the future in server.R . Once these lines are uncommented, the callback will never be called. Obviously, this is not really useful in the context of this example, but I think it would be very useful in general.

ui.R :

 library(shiny) shinyUI(fluidPage( sidebarLayout( sidebarPanel( sliderInput("max", "Max random number:", min = 1, max = 50, value = 30) ), mainPanel( verbatimTextOutput('output'), plotOutput('plot') ) ), tags$script(' Shiny.addCustomMessageHandler("setText", function(text) { document.getElementById("output").innerHTML = text; }) ') )) 

server.R :

 library(shiny) library(future) plan(multiprocess) shinyServer(function(input, output, session) { output$plot <- reactive({ max <- input$max #f <- future({ session$sendCustomMessage(type='setText', 'Please wait') Sys.sleep(3) x <- runif(1,0,max) session$sendCustomMessage(type='setText', paste('Your random number is', x)) return(NULL) #}) }) }) 
+3
source share
3 answers

Here's a solution on how to use the future package in a brilliant application. It is possible to execute several sessions without a session blocking another session when starting a computationally intensive task or waiting for the completion of the SQL query. I suggest opening two sessions (just open http://127.0.0.1:14072/ on two tabs) and play with buttons to test the functionality.

run_app.R :

 library(shiny) library(future) library(shinyjs) runApp(host = "127.0.0.1", port = 14072, launch.browser = TRUE) 

ui.R :

 ui <- fluidPage( useShinyjs(), textOutput("existsFutureData"), numericInput("duration", "Duration", value = 5, min = 0), actionButton("start_proc", h5("get data")), actionButton("start_proc_future", h5("get data using future")), checkboxInput("checkbox_syssleep", label = "Use Sys.sleep", value = FALSE), h5('Table data'), dataTableOutput('tableData'), h5('Table future data'), dataTableOutput('tableFutureData') ) 

server.R :

 plan(multiprocess) fakeDataProcessing <- function(duration, sys_sleep = FALSE) { if(sys_sleep) { Sys.sleep(duration) } else { current_time <- Sys.time() while (current_time + duration > Sys.time()) { } } return(data.frame(test = Sys.time())) } #fakeDataProcessing(5) ############################ SERVER ############################ server <- function(input, output, session) { values <- reactiveValues(runFutureData = FALSE, futureDataLoaded = 0L) future.env <- new.env() output$existsFutureData <- renderText({ paste0("exists(futureData): ", exists("futureData", envir = future.env)," | futureDataLoaded: ", values$futureDataLoaded) }) get_data <- reactive({ if (input$start_proc > 0) { shinyjs::disable("start_proc") isolate({ data <- fakeDataProcessing(input$duration) }) shinyjs::enable("start_proc") data } }) observeEvent(input$start_proc_future, { shinyjs::disable("start_proc_future") duration <- input$duration # This variable needs to be created for use in future object. When using fakeDataProcessing(input$duration) an error occurs: 'Warning: Error in : Operation not allowed without an active reactive context.' checkbox_syssleep <- input$checkbox_syssleep future.env$futureData %<-% fakeDataProcessing(duration, sys_sleep = checkbox_syssleep) future.env$futureDataObj <- futureOf(future.env$futureData) values$runFutureData <- TRUE check_if_future_data_is_loaded$resume() }, ignoreNULL = TRUE, ignoreInit = TRUE ) check_if_future_data_is_loaded <- observe({ invalidateLater(1000) if (resolved(future.env$futureDataObj)) { check_if_future_data_is_loaded$suspend() values$futureDataLoaded <- values$futureDataLoaded + 1L values$runFutureData <- FALSE shinyjs::enable("start_proc_future") } }, suspended = TRUE) get_futureData <- reactive({ if(values$futureDataLoaded > 0) future.env$futureData }) output$tableData <- renderDataTable(get_data()) output$tableFutureData <- renderDataTable(get_futureData()) session$onSessionEnded(function() { check_if_future_data_is_loaded$suspend() }) } 
+6
source

I redefined AndrΓ© le Blond's excellent answer and made a point showing a common asynchronous task processor, which can be used either by itself or with Shiny: FutureTaskProcessor.R

Note that it contains two files: FutureProcessor.R , which is a stand-alone asynchronous task handler, and app.R , which is a brilliant application showing how to use the async handler in Shiny.

+2
source

One admittedly complex workaround for the single-threaded nature of R in Shiny applications is to do the following:

  • Disconnect the external R process (run another R script located in the Shiny application directory or any directory accessible from the Brilliant Session) from inside R (I already tried this splitting and it works).
  • Configure the script to output its results to the temp directory (assuming that you are using Shiny on a Unix-based system) and give the output file a unique file name (preferably a name in the namespace of the current session (ie "/ tmp / [ID CHEMICAL SESSION] _example_output_file.RData ".
  • Use the Shiny invalidateLater () function to check for the existence of this output file.
  • Upload the output file to the Shiny workspace.
  • Finally, garbage collection by deleting the generated output file after loading.

Hope this helps.

0
source

All Articles