![](/img/trans.png)
[英]Use functions of an R package loaded with load_all() in callr::r() and callr::r_bg()
[英]How to use callr::r_bg within a downloadHandler in a Shiny App
我用下面的最小示例模拟的场景是允许用户在长时间运行下载时使用 Shiny 应用程序(单击
numericInput<\/code>控件并查看发生的服务器端事件)(使用
Sys.sleep(10)<\/code>在
downloadHandler<\/code>中)。
在同步设置中,当单击“下载”按钮时,用户仍然可以与 UI 元素交互,但其他 Shiny 计算(在本例中为
renderText<\/code> )会被放入队列中。
我想要异步设置,下载发生在后台,用户仍然可以与 UI 元素交互并<\/strong>获得所需的输出(例如
renderText<\/code> )。
我正在使用
callr::r_bg()<\/code>在 Shiny 中实现异步,但问题是我当前的 downloadHandler 代码不正确(应该下载
mtcars<\/code> ,但代码无法完成下载,404 错误消息) ,我相信这是由于
downloadHandler<\/code>期望编写
content()<\/code>函数的特定方式,而我编写
callr::r_bg()<\/code>的方式并不能很好地适应它。
任何见解将不胜感激!
参考:<\/strong>
https:\/\/www.r-bloggers.com\/2020\/04\/asynchronous-background-execution-in-shiny-using-callr\/<\/a>
最小的例子:<\/strong>
library(shiny)
ui <- fluidPage(
downloadButton("download", "Download"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text")
)
server <- function(input, output, session) {
long_download <- function(file) {
Sys.sleep(10)
write.csv(mtcars, file)
}
output$download <- downloadHandler(
filename = "data.csv",
content = function(file) {
x <- callr::r_bg(
func = long_download,
args = list(file)
)
return(x)
}
)
observeEvent(input$count, {
output$text <- renderText({
paste(input$count)
})
})
}
shinyApp(ui, server)
我想出了一个解决方案,并学到了以下几点:
input$X
,所以很难以传统方式包含反应性。 解决方法是将 UI 呈现为隐藏的actionButton
,被用户将看到的downlodButton
掩盖。 在以下过程中促进了反应性:用户单击 actionButton -> 反应性更新 -> 当反应性完成时( reactive()$is_alive() == FALSE
),使用shinyjs::click
启动downloadHandler
callr
函数放在 downloadHandler 中,而是将文件保存在内容 arg 中。 范围界定似乎有些困难,因为文件需要在content
功能环境中可用reactive()$is_alive()
invalidateLater()
和全局变量( download_once
)的切换对于防止反应性不断激活很重要。 没有它,将会发生什么是您的浏览器会无限地不断下载文件——这种行为是可怕的,对于您的 Shiny 应用程序用户来说,它看起来像病毒一样!代码解决方案:
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
download_once <- TRUE
#output that tracks progress of background task
check <- reactive({
invalidateLater(millis = 1000, session = session)
if (long_run()$is_alive()) {
x <- "Job running in background"
} else {
x <- "Async job in background completed"
if(isTRUE(test)) {
shinyjs::click("download")
download_once <<- FALSE
}
invalidateLater(millis = 1, session = session)
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
感谢@latlio 的精彩回答。 我认为它的云很容易改进。 invalidateLater
应该非常小心地使用,并且只在需要时使用。 我只有一次invalidateLater
并将其移至我们正在等待结果的逻辑部分。 因此,我们不会无限地使反应性无效。
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
#output that tracks progress of background task
check <- reactive({
if (long_run()$is_alive()) {
x <- "Job running in background"
invalidateLater(millis = 1000, session = session)
} else {
x <- "Async job in background completed"
shinyjs::click("download")
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.