如何将Shiny中reactive()改为用ExtendedTask和mirai实现异步?
问题描述
我有一个简单的Shiny应用,原本在reactive()调用中创建数据集并绘图。由于数据创建可能耗时较长,我希望将其改为异步调用,以便会话可处理其他操作。为此,我想使用shiny::ExtendedTask搭配mirai。
官方文档中有task$invoke(...)通过按钮触发(例如在shiny::observeEvent()内)的示例,但没有说明如何在reactive()触发的场景下进行结构设计。我的实际场景需要在特定页面的多个绘图中复用生成的数据。
同步应用最小可复现示例(MWE)
以下是阻塞式应用的示例:
library(shiny) library(bslib) create_data <- function(n) { Sys.sleep(2) data.frame(x = seq(n), y = rnorm(n)) } ui <- page_fillable( card( card_header(span("为展示响应状态,当前时间:", textOutput("current_time", inline = TRUE))), plotOutput("myplot") ) ) server <- function(input, output, session) { # 此处的reactive需要替换为ExtendedTask data <- reactive({ create_data(100) }) output$myplot <- renderPlot({ d <- data() plot(d$x, d$y) }) # 展示会话是否空闲:实时更新当前时间 output$current_time <- renderText({ invalidateLater(1000, session) format(Sys.time(), "%H:%M:%S") }) } shinyApp(ui, server)
注意由于create_data()函数会阻塞会话,时间不会立即更新显示。
异步应用尝试
为展示事件触发时机,我将内容移至第二个导航栏页面,期望在页面显示、数据需要时触发任务(即reactive()而非observe()的行为)。我的尝试未成功,当前用observe()触发task$invoke的方式会立即执行,而非由绘图渲染触发,示例代码如下:
library(shiny) library(bslib) library(mirai) create_data <- function(n) { Sys.sleep(2) data.frame(x = seq(n), y = rnorm(n)) } # 日志辅助函数 flog <- function(m) cat(sprintf("INFO [%s] | %s\n", format(Sys.time(), "%Y-%m-%d %H:%M:%OS3"), m)) ui <- page_navbar( nav_panel("默认空页面"), nav_panel( "与之前一致的页面", card_header(span("为展示响应状态,当前时间:", textOutput("current_time", inline = TRUE))), plotOutput("myplot") ) ) server <- function(input, output, session) { # 创建任务 task <- ExtendedTask$new(function(...) mirai(fun(n = n), fun = create_data, ...)) # 问题所在:observe并非由绘图渲染触发,而是立即执行 observe({ flog("任务调用") task$invoke(n = 100) flog("任务调用完成") }) output$myplot <- renderPlot({ flog("获取任务结果") data <- task$result() flog("获取任务结果完成") plot(data$x, data$y) }) # 展示会话是否空闲:实时更新当前时间 output$current_time <- renderText({ invalidateLater(1000, session) format(Sys.time(), "%H:%M:%S") }) } shinyApp(ui, server)
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

