如何在Shiny应用的downloadHandler中使用callr::r_bg实现非阻塞异步下载
问题根因
你当前写法的核心问题在于:downloadHandler的content()函数要求函数执行结束前,必须将待下载的内容完整写入传入的file路径,Shiny才会将该文件返回给前端触发下载。你直接启动callr::r_bg()后台进程后立刻返回,此时后台任务还在执行,文件尚未生成,Shiny读取不到有效文件就会返回404错误。
同时你需要的「下载不阻塞当前应用其他交互」的需求,需要配合Shiny的异步promises机制实现,避免同步等待后台任务时卡住主线程。
修正后的可运行代码
library(shiny) library(promises) library(callr) 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) return(TRUE) } output$download <- downloadHandler( filename = "data.csv", content = function(file) { # 启动后台任务 bg_task <- callr::r_bg( func = long_download, args = list(file) ) # 返回promise异步等待任务完成,不阻塞主线程 promises::promise(function(resolve, reject) { check_task <- function() { if (bg_task$is_alive()) { # 每100ms轮询一次任务状态 later::later(check_task, delay = 0.1) } else { tryCatch({ bg_task$get_result() resolve(TRUE) }, error = function(e) { reject(e) }) } } check_task() }) } ) observeEvent(input$count, { output$text <- renderText({ paste(input$count) }) }) } shinyApp(ui, server)
实现效果
上述代码运行后:
- 点击下载按钮后,10秒等待期内可自由调整
numericInput的值,textOutput会实时更新,不会被下载任务阻塞 - 后台任务完成后会自动触发浏览器下载
data.csv文件,不会出现404错误
如果你使用Shiny 1.8.0及以上版本,也可以改用官方提供的
ExtendedTask接口实现后台任务,代码逻辑会更简洁易维护。
内容的提问来源于stack exchange,提问作者latlio
相关产品推荐
相关产品推荐

