如何在 Shiny 应用程序结束时终止所有进程?

Posted

技术标签:

【中文标题】如何在 Shiny 应用程序结束时终止所有进程?【英文标题】:How does one terminate all processes upon Shiny app end? 【发布时间】:2021-11-15 22:38:24 【问题描述】:

我正在使用安装了本地服务器的 Shiny。

我的 Shiny 应用使用 system/system2/processx::run 运行繁重的本地程序。我正在同步运行它(等待=T)。如果用户关闭 Shiny 的浏览器窗口,我希望这个繁重的程序结束。如果用户重新打开浏览器窗口,我希望 Shiny 应用再次准备好执行本地程序。

如何做到这一点?

当我使用 system/system2/processx::run 时,应用程序似乎在等待繁重的程序完成并且在关闭时不会停止它。

代表:

library(shiny)
library(processx)

ui <- fluidPage(
  actionButton("runBtn", label="Run a program that consumes many resources") ,
)

server <- function(input, output, session) 
  observeEvent(input$runBtn,
      run("sleep", "240"))

shinyApp(ui, server)

当我使用 reprex 关闭浏览器窗口,然后尝试重新打开它时,过程结束需要一些时间。我希望它或多或少立即可用。

附:我正在使用 Linux;系统特定的 hack 很好。

【问题讨论】:

看看session 对象,它有几个可以使用的回调,例如onSessionEnded shiny.rstudio.com/reference/shiny/latest/session.html。然后你也许可以用system命令stat.ethz.ch/R-manual/R-devel/library/base/html/system.html杀死后台脚本的PID @PorkChop 谢谢。但是,我需要该进程同步运行,而不是作为后台进程。我需要在进程运行时暂停应用程序,因此这是实现此目的的自然方式。 【参考方案1】:

@PorkChop 的评论指出了正确的方向。但是,我建议使用processx::process 而不是run,因为它为我们提供了从R 中控制启动进程的方法。请参阅?process。 (run顺便也是基于进程类的。)

这里的主要问题是,同步运行进程 (wait=TRUE) 会阻塞 R 会话。因此,onStop 在 R 重新控制之前不会触发。 因此,一旦关闭浏览器窗口,您将无法触发任何事情,因为闪亮会话会继续运行,直到外部程序完成并且 R 可以关闭闪亮会话。

在会话结束时,以下代码检查异步启动的进程是否仍然存在,并在必要时将其终止(仅在 Windows 上测试)。

library(shiny)
library(processx)

ui <- fluidPage(
  actionButton("runBtn", label="Run a program that consumes many resources"),
  actionButton("stopSession", "Stop session")
)

server <- function(input, output, session) 
  
  myProcess <- NULL
  
  observeEvent(input$stopSession, 
    cat(sprintf("Closing session %s\n", session$token))
    session$close()
  )
  
  observeEvent(input$runBtn,
               
                 if(Sys.info()[["sysname"]]=="Windows")
                   writeLines(text = c("ping 127.0.0.1 -n 60 > nul"), con = "sleep.bat")
                   myProcess <<- process$new("cmd.exe", c("/c", "call", "sleep.bat"), supervise = TRUE, stdout = "")
                  else 
                   myProcess <<- process$new("sleep", "60", supervise = TRUE, stdout = "")
                 
                 # myProcess$wait() # wait for the process to finish
               )
  
  onStop(function()
    cat(sprintf("Session %s was closed\n", session$token))
    if(!is.null(myProcess))
      if(myProcess$is_alive())
        myProcess$kill()
      
    
    
  )


shinyApp(ui, server)

关于不同的会话回调函数见related post。


按照此处的要求,该过程包含在reactiveVal

library(shiny)
library(processx)

ui <- fluidPage(
  actionButton("runBtn", label="Run a program that consumes many resources"),
  actionButton("stopSession", "Stop session")
)

server <- function(input, output, session) 
  
  myProcess <- reactiveVal(NULL)
  
  observeEvent(input$stopSession, 
    cat(sprintf("Closing session %s\n", session$token))
    session$close()
  )
  
  observeEvent(input$runBtn,
               
                 if(Sys.info()[["sysname"]]=="Windows")
                   writeLines(text = c("ping 127.0.0.1 -n 60 > nul"), con = "sleep.bat")
                   myProcess(process$new("cmd.exe", c("/c", "call", "sleep.bat"), supervise = TRUE, stdout = ""))
                  else 
                   myProcess(process$new("sleep", "60", supervise = TRUE, stdout = ""))
                 
                 # myProcess()$wait() # wait for the process to finish
               )
  
  onStop(function()
    cat(sprintf("Session %s was closed\n", session$token))
    if(!is.null(isolate(myProcess())))
      if(isolate(myProcess()$is_alive()))
        isolate(myProcess()$kill())
      
    
    
  )


shinyApp(ui, server)

【讨论】:

谢谢。但是我需要该进程同步运行,而不是在后台运行。 (这意味着只要进程运行,应用程序就不应运行)。我想我可以手动挂起应用程序直到进程完成,但更简单的解决方案是在会话结束时停止同步进程(如果可能)。 在我看来,这就是您的代码的问题。当您同步运行该进程时,R 会话被阻止。因此,onStop 在 R 重新控制之前不会触发。因此,一旦关闭浏览器窗口,您将无法触发任何事情,因为闪亮会话会一直运行,直到外部程序完成并且 R 可以关闭闪亮会话。如果您希望 R 终止该进程,则需要时间来运行某些东西。您可以通过process$wait() 测试此语句 - 请查看我的编辑。 因此,我宁愿异步运行进程并在进程处于活动状态时禁用所需的 UI 元素(这应该没什么大不了的 - 例如,请参阅 this)。 是的,主管会在R退出后杀死进程。但是,结束闪亮会话(例如通过关闭浏览器选项卡)不会结束 R 会话。服务器功能继续运行,等待下一个闪亮会话。 我认为将其包装在 reactiveVal 中没有任何优势。

以上是关于如何在 Shiny 应用程序结束时终止所有进程?的主要内容,如果未能解决你的问题,请参考以下文章

进程结束时如何保持 QProcess 执行的命令?

4-5 Linux 中断进程 --- kill (kill -2 实验)

使用 MPI 终止所有进程

c++11的线程,怎么终止

如何等待所有节点流完成/结束?

如何用CMD命令终止和启动进程?