从未来调用闪亮的 JavaScript 回调
Calling a shiny JavaScript Callback from within a future
在 shiny 中,可以从服务器逻辑调用 javascript 中编写的客户端回调。在 ui.R
中说你有一些 JavaScript 包括一个名为 setText
:
的函数
tags$script('
Shiny.addCustomMessageHandler("setText", function(text) {
document.getElementById("output").innerHTML = text;
})
')
然后在您的 server.R
中您可以调用 session$sendCustomMessage(type='foo', 'foo')
。
假设我有一个 long-运行ning 函数,其中 returns 一些数据要绘制。如果我正常执行此操作,R 线程在 运行 执行此函数时会很忙,因此此时无法处理其他请求。能够使用 futures 包 运行 这个函数真的很有用,这样它 运行 就可以异步到代码,并异步调用回调。但是,当我尝试时,这似乎不起作用。
抱歉,如果这不是很清楚。作为一个简单的示例,在您取消注释试图在 server.R
中调用 future 的两行之前,以下内容应该有效。一旦这些行被取消注释,回调就永远不会被调用。显然它在这个例子的上下文中实际上没有用,但我认为它在一般情况下会非常有用。
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)
#})
})
})
一个 诚然 复杂的解决方法是在 Shiny 应用程序中对 R 的 single-threaded 性质进行以下操作:
- 分离外部 R 进程(运行 另一个 R 脚本位于
闪亮的应用程序目录,或可从中访问的任何目录
R 中的 Shiny session) (我以前试过这种分裂,
并且有效)。
- 配置该脚本以将其结果输出到临时目录(假设您在 Unix-based 系统上 运行ning Shiny)并为输出文件指定一个唯一的文件名(最好在当前 session 的命名空间(即“/tmp/[SHINY SESSION HASH ID]_example_output_file.RData”。
- 使用 Shiny 的 invalidateLater() 函数检查输出文件是否存在。
- 将输出文件加载到 Shiny session 工作区。
- 最后,通过删除加载后生成的输出文件进行垃圾收集。
希望对您有所帮助。
这是一个关于如何在闪亮的应用程序中使用未来包的解决方案。
当 运行 计算密集型任务或等待 sql 查询完成时,可能有多个会话没有会话阻塞另一个会话。我建议打开两个会话(只需在两个选项卡中打开 http://127.0.0.1:14072/)并使用按钮来测试功能。
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()
})
}
我改造了 André le Blond 的 to and made a gist showing a generic asynchronous task processor which can be used either by itself or with Shiny: FutureTaskProcessor.R
注意它包含两个文件:FutureProcessor.R which is the stand alone asynchronous task handler and app.R 这是一个 Shiny 应用程序,展示了 Shiny 中异步处理程序的使用。
在 shiny 中,可以从服务器逻辑调用 javascript 中编写的客户端回调。在 ui.R
中说你有一些 JavaScript 包括一个名为 setText
:
tags$script('
Shiny.addCustomMessageHandler("setText", function(text) {
document.getElementById("output").innerHTML = text;
})
')
然后在您的 server.R
中您可以调用 session$sendCustomMessage(type='foo', 'foo')
。
假设我有一个 long-运行ning 函数,其中 returns 一些数据要绘制。如果我正常执行此操作,R 线程在 运行 执行此函数时会很忙,因此此时无法处理其他请求。能够使用 futures 包 运行 这个函数真的很有用,这样它 运行 就可以异步到代码,并异步调用回调。但是,当我尝试时,这似乎不起作用。
抱歉,如果这不是很清楚。作为一个简单的示例,在您取消注释试图在 server.R
中调用 future 的两行之前,以下内容应该有效。一旦这些行被取消注释,回调就永远不会被调用。显然它在这个例子的上下文中实际上没有用,但我认为它在一般情况下会非常有用。
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)
#})
})
})
一个 诚然 复杂的解决方法是在 Shiny 应用程序中对 R 的 single-threaded 性质进行以下操作:
- 分离外部 R 进程(运行 另一个 R 脚本位于 闪亮的应用程序目录,或可从中访问的任何目录 R 中的 Shiny session) (我以前试过这种分裂, 并且有效)。
- 配置该脚本以将其结果输出到临时目录(假设您在 Unix-based 系统上 运行ning Shiny)并为输出文件指定一个唯一的文件名(最好在当前 session 的命名空间(即“/tmp/[SHINY SESSION HASH ID]_example_output_file.RData”。
- 使用 Shiny 的 invalidateLater() 函数检查输出文件是否存在。
- 将输出文件加载到 Shiny session 工作区。
- 最后,通过删除加载后生成的输出文件进行垃圾收集。
希望对您有所帮助。
这是一个关于如何在闪亮的应用程序中使用未来包的解决方案。 当 运行 计算密集型任务或等待 sql 查询完成时,可能有多个会话没有会话阻塞另一个会话。我建议打开两个会话(只需在两个选项卡中打开 http://127.0.0.1:14072/)并使用按钮来测试功能。
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()
})
}
我改造了 André le Blond 的
注意它包含两个文件:FutureProcessor.R which is the stand alone asynchronous task handler and app.R 这是一个 Shiny 应用程序,展示了 Shiny 中异步处理程序的使用。