vielleicht auch nicht genau das, was Sie suchen, konnte aber den Trick (zumindest auf m richtiges Linux). Für mich funktioniert es so, wie ich es möchte, da ich Bash-Skripte verwende, die von R-Shiny ausgelöst werden und ich möchte sie abbrechen können. Wie wäre es, wenn Sie Ihren R-Code in ein Skript schreiben und das Skript mit dem Systembefehl auslösen würden?
Im folgenden Beispiel verwende ich einfach ein dummy Bash-Skript, das einen Schlafbefehl ausführt, während das erste CL-Argument die Menge an Schlaf ist. Alles unter 10 Sekunden wird nicht akzeptiert und setzt den Exit-Status auf 1. Zusätzlich erhalte ich eine Ausgabe in einer Logdatei, die ich überwachen kann, und damit den Fortschritt in Echtzeit.
Ich hoffe, Sie finden das hilfreich.
library(shiny)
ui <- fluidPage(
# we need this to send costumized messages
tags$head(tags$script(HTML('Shiny.addCustomMessageHandler("jsCode",function(message) {eval(message.value);});'))),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
textInput("duration", "How long you want to wait?"),hr(),
p("Are you experienced?"),
actionButton("processbtn", "Yes"),hr(),
p("Show me what's going on"),
actionButton("logbtn", "Show me by clicking here."),hr(),
p("Tired of being experienced?"),
actionButton("abortbtn", "Yes")
), # close sidebar panel
# Show a plot of the generated distribution
mainPanel(
textOutput("outText"),hr(),
verbatimTextOutput("outLog")
) # close mainpanel
) # close sidebar
) # close fluidpage
#------SERVER------------
# Define server logic required to draw a histogram
server <- function(input, output, session) {
# our reactive values that change on button click by the observe functions below
values <- reactiveValues(process = 0, abort = 0, log = 0)
observeEvent(input$processbtn, {
values$process = 1
values$abort = 0
values$log = 0
})
observeEvent(input$abortbtn, {
values$process = 0
values$abort = 1
})
observeEvent(input$logbtn, {
values$log = 1
})
current_state = function(exitfile) {
# get the pid
pid = as.integer(system2("ps", args = "-ef | grep \"bash ~/dummy_script.sh\" | grep -v grep | awk '{print $2}'", stdout = TRUE))
print(pid)
if (length(pid) > 0)
return("RUNNING")
if (file.exists(exitfile))
return("TERMINATED")
return("NOT_STARTED")
}
start_function = function(exitfile) {
if(input$duration == "") {
end_message="The text input field is empty!"
js_string <- 'alert("SUCCESS");'
js_string <- sub("SUCCESS",end_message,js_string)
session$sendCustomMessage(type='jsCode', list(value = js_string))
values$process = 0
return("NOT_STARTED")
} else { # all checks are fine. send a message and start processing
end_message="We start waiting, yeah!!!"
js_string <- 'alert("SUCCESS");'
js_string <- sub("SUCCESS",end_message,js_string)
session$sendCustomMessage(type='jsCode', list(value = js_string))
# here we execute the outsourced script and
# write the exit status to a file, so we can check for that and give an error message
system(paste("(bash ~/dummy_script.sh", input$duration,"; echo $? >", exitfile, ")"), wait = FALSE)
return("RUNNING")
}
}
on_terminated = function(exitfile) {
# get the exit state of the script
status = readLines(exitfile)
print(status)
# we want to remove the exit file for the next run
unlink(exitfile, force = TRUE)
# message when we finished
if (status != 0){
end_message="Duration is too short."
js_string <- 'alert("SUCCESS");'
js_string <- sub("SUCCESS",end_message,js_string)
session$sendCustomMessage(type='jsCode', list(value = js_string))
}
else {
end_message="Success"
js_string <- 'alert("SUCCESS");'
js_string <- sub("SUCCESS",end_message,js_string)
session$sendCustomMessage(type='jsCode', list(value = js_string))
}
values$process = 0
}
# our main processing fucntion
output$outText = renderText({
# trigger processing when action button clicked
if(values$process) {
# get the homefolder
homedir=Sys.getenv("HOME")
# create the path for an exit file (we'll need to evaluate the end of the script)
exitfile=file.path(homedir, "dummy_exit")
print(exitfile)
state = current_state(exitfile) # Can be NOT_STARTED, RUNNING, COMPLETED
print(state)
if (state == "NOT_STARTED")
state = start_function(exitfile)
if (state == "RUNNING")
invalidateLater(2000, session = getDefaultReactiveDomain())
if (state == "TERMINATED")
on_terminated(exitfile)
# Abort processing
} else
if(values$abort) {
pid = as.integer(system2("ps", args = "-ef | grep \"bash ~/dummy_script.sh\" | grep -v grep | awk '{print $2}'", stdout = TRUE))
print(pid)
system(paste("kill", pid), wait = FALSE)
}
}) # close renderText function
output$outLog = renderText({
if(values$log) {
homedir=Sys.getenv("HOME")
logfile=file.path(homedir, "/dummy_log")
if(file.exists(logfile)){
invalidateLater(2000)
paste(readLines(logfile), collapse = "\n")
}
else {
print("Nothing going on here")
}
}
})
} # close server
# Run the application
shinyApp(ui = ui, server = server)
Ich habe eine Schaltfläche "Analyse", die einige Minuten dauern kann. Manchmal merke ich, dass ich vergessen habe, eine Option festzulegen, und ich möchte sie abbrechen, damit ich eine kleine Anpassung vornehmen kann. Es ist unpraktisch, die App zu neustarten, um neu zu starten. Ich müsste den gesamten Prozess noch einmal durchgehen. Und es sieht so aus, als ob die Sitzung selbst beendet wird (wenn ich das Fenster schließe, in dem der "Analyse" -Knopf angeklickt wurde, läuft der Code immer noch, zumindest in diesem Fall würde ich die Anfrage gerne beenden. –
I frage mich, ob du eine "tipy-falle" in "analyse" integrieren kannst, die auf ein bestimmtes Ereignis (wie einen Knopfdruck) wartet und den Code unterbricht. –
Ich nehme an, du könntest zum Beispiel ein globales boolesches Flag haben und in "analysiere" dich würde die Markierung regelmäßig überprüfen.So können Sie eine Hacky-Lösung machen, vorausgesetzt, Sie haben Zugriff auf den Code, der die lange Berechnung ausführt.Wenn Sie eine Funktion aufrufen, die nicht von Ihnen geschrieben wurde, sehe ich nicht, wie Sie das tun könnten –