这个问题是这个问题的延续:Is it possible to stop executing of R code inside shiny (without stopping the shiny process)?.

我在应用程序中显示的绘图需要一些时间才能生成,我希望用户能够停止创建(例如,如果他们在选项中出错).我在Shiny中找到了this blog post个关于使用callr的答案.工作流程如下所示:

  • 创建作业(job)/绘图的空列表
  • 点击"开始"创建一个后台进程来创建绘图

首先,我不确定当几个人同时使用该应用程序时,它将如何扩展.由于每个后台进程都是独立的,我不认为一个用户会阻止其他用户,但我可能错了.

其次,我想在绘图上显示一个等待指示器.到目前为止,我使用包waiter来实现这一点,但这里的问题是,renderPlot()每秒都会失效,以判断后台进程是否完成.因此,当输出无效时,waiter会反复出现和消失.

下面是一个模仿我想要的行为的示例应用程序:

library(shiny)
library(uuid)
library(ggplot2)
library(waiter)

ui <- fluidPage(
  useWaiter(),
  titlePanel("Test background job"),
  actionButton("start","Start Job"),
  actionButton("stop", "Stop job"),
  plotOutput("plot")
)

# the toy example job
slow_func <- function(var){
  library(ggplot2)
  Sys.sleep(5)
  ggplot(mtcars, aes(drat, !!sym(var))) + 
    geom_point()
}

server <- function(input, output, session) {
  
  w <- Waiter$new(id = "plot")

  token <- reactiveValues(var = NULL, id = NULL, last_id = NULL)
  jobs <- reactiveValues()
  
  
  # When I press "start", run the slow function and append the output to
  # the list of jobs. To render the plot, check if the background process is
  # finished. If it's not, re-check one second later.
  
  long_run <- eventReactive(input$start, {
    token$var <- c(token$var, sample(names(mtcars), 1))
    token$id <- c(token$id, UUIDgenerate())
    token$last_id <- token$id[[length(token$id)]]
    message(paste0("running task with id: ", token$last_id))
    jobs[[token$last_id]] <- callr::r_bg(
      func = slow_func,
      args = list(var = token$var[[length(token$var)]])
    )
    return(jobs[[token$last_id]])
  })
  
  observeEvent(input$start, {
    output$plot <- renderPlot({
      w$show()
      if (long_run()$poll_io(0)["process"] == "timeout") {
        invalidateLater(1000)
      } else {
        jobs[[token$last_id]]$get_result()
      }
    })
  })
  
  # When I press "stop", kill the last process, remove it from the list of
  # jobs (because it didn't produce any output so it is useless), and display 
  # the last process (which by definition is the last plot produced)
  
  observeEvent(input$stop, {
    
    if (length(token$id) > 0) {
      jobs[[token$last_id]]$kill()
      message(paste0("task ", token$last_id, " stopped"))
      token$id <- token$id[-length(token$id)]
      if (length(token$id) > 0) {
        token$last_id <- token$id[[length(token$id)]]
      }
    }
    
    output$plot <- renderPlot({
      if (length(token$id) > 0) {
        print(token$last_id)
        jobs[[token$last_id]]$get_result()
      } else {
        return(NULL)
      }
    })
  })
  
}

shinyApp(ui = ui, server = server)

Current behavior:

  • 运行应用程序,点击"开始工作"
  • 请注意,waiter覆盖显示和消失

Question:在后台计算时,如何在绘图上获得恒定加载屏幕?

推荐答案

关于你的第一个担忧:这种方法不会阻碍其他课程.然而,通过invalidateLater()进行的轮询将产生一些负载.

在这种情况下,一个很好的图书馆是ipc及其introductory vignette.

关于第二个问题:这种行为有一个简单的解决方法.我们可以使用req及其cancelOutput参数-参见?req:

cancelOutput:如果为TRUE且正在计算输出,则停止

library(shiny)
library(uuid)
library(ggplot2)
library(waiter)

ui <- fluidPage(
  useWaiter(),
  titlePanel("Test background job"),
  actionButton("start","Start Job"),
  actionButton("stop", "Stop job"),
  plotOutput("plot")
)

# the toy example job
slow_func <- function(var){
  library(ggplot2)
  Sys.sleep(5)
  ggplot(mtcars, aes(drat, !!sym(var))) + 
    geom_point()
}

server <- function(input, output, session) {
  
  w <- Waiter$new(id = "plot")
  
  token <- reactiveValues(var = NULL, id = NULL, last_id = NULL)
  jobs <- reactiveValues()
  
  
  # When I press "start", run the slow function and append the output to
  # the list of jobs. To render the plot, check if the background process is
  # finished. If it's not, re-check one second later.
  
  long_run <- eventReactive(input$start, {
    token$var <- c(token$var, sample(names(mtcars), 1))
    token$id <- c(token$id, UUIDgenerate())
    token$last_id <- token$id[[length(token$id)]]
    message(paste0("running task with id: ", token$last_id))
    jobs[[token$last_id]] <- callr::r_bg(
      func = slow_func,
      args = list(var = token$var[[length(token$var)]])
    )
    return(jobs[[token$last_id]])
  })
  
  observeEvent(input$start, {
    output$plot <- renderPlot({
      w$show()
      if (long_run()$poll_io(0)["process"] == "timeout") {
        invalidateLater(1000)
        req(FALSE, cancelOutput = TRUE)
      } else {
        jobs[[token$last_id]]$get_result()
      }
    })
  })
  
  # When I press "stop", kill the last process, remove it from the list of
  # jobs (because it didn't produce any output so it is useless), and display 
  # the last process (which by definition is the last plot produced)
  
  observeEvent(input$stop, {
    
    if (length(token$id) > 0) {
      jobs[[token$last_id]]$kill()
      message(paste0("task ", token$last_id, " stopped"))
      token$id <- token$id[-length(token$id)]
      if (length(token$id) > 0) {
        token$last_id <- token$id[[length(token$id)]]
      }
    }
    
    output$plot <- renderPlot({
      if (length(token$id) > 0) {
        print(token$last_id)
        jobs[[token$last_id]]$get_result()
      } else {
        return(NULL)
      }
    })
  })
  
}

shinyApp(ui = ui, server = server)

R相关问答推荐

R:将列名的字符载体传递给可以 Select 接受多个参数的函数

用apply/map/etch替换循环以加快速度

如何使用geom_sf在边界显示两种 colored颜色 ?

如何从当前行上方找到符合特定条件的最接近值?

如何编辑ggplot的图例字使用自定义对象(gtable)?'

整数成随机顺序与约束R?

derrr mutate case_when grepl不能在R中正确返回值

如何写一个R函数来旋转最后n分钟?

单个轮廓重叠条的单独图例

bslib::card_header中的shine::downloadButton,图标而不是文本

将数字转换为分钟和秒

从外部文件读取多个值作为字符向量

无法正确设置动态创建的Quarto标注的格式

使用范围和单个数字将数字与字符串进行比较

Data.table';S GForce-将多个函数应用于多列(带可选参数)

以字符格式导入的ExcelElectron 表格日期列标题

悬崖三角洲超大型群数计算导致整数溢出

通过初始的shiny 应用更新部署的shiny 应用的数据和参数,其中部署的应用程序显示为URL

在ggploy中创建GeV分布时出错

在使用SliderInput In Shiny(R)设置输入数据的子集时,保留一些情节痕迹