How to Show a Loading Screen When the Output Is Being Calculated in a Background Process

How to show a loading screen when the output is being calculated in a background process?

Regarding your first concern: this approach won't block other sessions. However, the polling via invalidateLater() will create some load.

A great library to look at in this context is ipc and its introductory vignette.

Regarding the second issue: There is a simple fix for this behaviour. We can use req and its cancelOutput parameter - see ?req:

cancelOutput: If TRUE and an output is being evaluated, stop
processing as usual but instead of clearing the output, leave it in
whatever state it happens to be in.

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)

running background process in R Shiny

As explained in the examples here, wait() means that we have to wait for the result of the background job before continuing the rest of the processes.

One way to keep the clock updating while running the background job is to use poll_io() to check whether the job is finished (note that it is better to use poll_io() than is_alive(), as explained in this Github comment). I have done something similar in this question, although the general app is a bit more complicated.

Here's what you need to modify in the server:

  observeEvent(input$startjob, {
global$result <- r_bg(func = printLS, supervise = F)
})

output$LS <- renderUI({
req(input$startjob)
if (global$result$poll_io(0)["process"] == "timeout") {
invalidateLater(1000)
} else {
p(HTML(paste(global$result$get_result(), sep = "", collapse = "<br>")))
}
})

Note that system2(command = "ls", args = c("-l"), stdout = TRUE, stderr = TRUE) didn't work on my laptop so I used system("ls -l") instead.

R shiny: display loading... message while function is running

I'm already using a simpler and more reliable way than the one I posted before.

A combination of

tags$style(type="text/css", "
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: #000000;
background-color: #CCFF66;
z-index: 105;
}
")

with

conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div("Loading...",id="loadmessage")
)

Example:

runApp(list(
ui = pageWithSidebar(
headerPanel("Test"),
sidebarPanel(
tags$head(tags$style(type="text/css", "
#loadmessage {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
padding: 5px 0px 5px 0px;
text-align: center;
font-weight: bold;
font-size: 100%;
color: #000000;
background-color: #CCFF66;
z-index: 105;
}
")),
numericInput('n', 'Number of obs', 100),
conditionalPanel(condition="$('html').hasClass('shiny-busy')",
tags$div("Loading...",id="loadmessage"))
),
mainPanel(plotOutput('plot'))
),
server = function(input, output) {
output$plot <- renderPlot({ Sys.sleep(2); hist(runif(input$n)) })
}
))

tags$head() is not required, but it's a good practice to keep all the styles inside head tag.

How to show a shiny modal only if a certain operation takes longer than expected?

Here is an example on what I've outlined in the comments. However, for a delay time of only 0.5s to show the modal, the overhead to create a background process might be too much.

library(shiny)
library(shinyjs)
library(callr)

ui <- fluidPage(
useShinyjs(),
actionButton("saveButton", "save"),
)

server <- function(input, output, session) {
rv <- reactiveValues(bg_process = NULL, retry_count = 0)

observeEvent(input$saveButton, {
disable("saveButton")
unknown_duration <- round(runif(1L, max = 2L), digits = 1)
print(paste0(Sys.time(), " - unknown process duration: ", unknown_duration, "s"))
rv$bg_process <- r_bg(
func = function(duration){
Sys.sleep(duration)
return("result")
},
args = list(duration = unknown_duration))
})

observe({
req(rv$bg_process)
if (rv$bg_process$poll_io(0)["process"] == "ready") {
print(paste(Sys.time(), "-", rv$bg_process$get_result()))
rv$retry_count <- 0
enable("saveButton")
removeModal()
if(rv$bg_process$is_alive() == FALSE){
rv$bg_process <- NULL # reset
}
} else {
invalidateLater(1000)
if(isolate({rv$retry_count}) == 3L){
print(paste(Sys.time(), "- showModal"))
showModal(modalDialog("Saving..."))
} else {
print(paste(Sys.time(), "- waiting"))
}
}
isolate({rv$retry_count <- rv$retry_count + 1})
})

}

shinyApp(ui, server)

Displaying something in kivy while a process is running background

Just found the solution thanks to this thread

you have to use the kivy Clock.schedule_once() method : kivy clock doc

I my case, I just create a function fermerbanc_schedule in charge of :

  • Displaying the waiting message on the screen
  • called the fermerBanc function via schedule_once
def fermerBanc_schedule(self, *args):
self.msgWarning = Label(text="Merci de patienter...", font_size='55sp', size=(100, 50), pos_hint={'center_x': .5, 'center_y':.5})
self.add_widget(self.msgWarning)
Clock.schedule_once(lambda dt: self.fermerBanc(self, *args), 0)

Display a loading bar before the entire page is loaded

Use a div #overlay with your loading info / .gif that will cover all your page:

<div id="overlay">
<img src="loading.gif" alt="Loading" />
Loading...
</div>

jQuery:

$(window).load(function(){
// PAGE IS FULLY LOADED
// FADE OUT YOUR OVERLAYING DIV
$('#overlay').fadeOut();
});

Here's an example with a Loading bar:

jsBin demo

;(function(){
function id(v){ return document.getElementById(v); }
function loadbar() {
var ovrl = id("overlay"),
prog = id("progress"),
stat = id("progstat"),
img = document.images,
c = 0,
tot = img.length;
if(tot == 0) return doneLoading();

function imgLoaded(){
c += 1;
var perc = ((100/tot*c) << 0) +"%";
prog.style.width = perc;
stat.innerHTML = "Loading "+ perc;
if(c===tot) return doneLoading();
}
function doneLoading(){
ovrl.style.opacity = 0;
setTimeout(function(){
ovrl.style.display = "none";
}, 1200);
}
for(var i=0; i<tot; i++) {
var tImg = new Image();
tImg.onload = imgLoaded;
tImg.onerror = imgLoaded;
tImg.src = img[i].src;
}
}
document.addEventListener('DOMContentLoaded', loadbar, false);
}());
*{margin:0;}
body{ font: 200 16px/1 sans-serif; }
img{ width:32.2%; }

#overlay{
position:fixed;
z-index:99999;
top:0;
left:0;
bottom:0;
right:0;
background:rgba(0,0,0,0.9);
transition: 1s 0.4s;
}
#progress{
height:1px;
background:#fff;
position:absolute;
width:0; /* will be increased by JS */
top:50%;
}
#progstat{
font-size:0.7em;
letter-spacing: 3px;
position:absolute;
top:50%;
margin-top:-40px;
width:100%;
text-align:center;
color:#fff;
}
<div id="overlay">
<div id="progstat"></div>
<div id="progress"></div>
</div>

<div id="container">
<img src="http://placehold.it/3000x3000/cf5">
</div>

Displaying stdout of a background process in specific location of the terminal

This is a simple solution if you're willing to accept output just above the current prompt line.

bufferout () {
local buffer
while IFS= read -r line; do # buffer stdin
buffer="$buffer$line\n"
done
print -rn -- $terminfo[dl1] # delete current line
printf "$buffer" # print buffer
kill -USR1 $$ # send USR1 when done
}

TRAPUSR1 () { # USR1 signal handler
zle -I # invalidate prompt
unhash -f TRAPUSR1 bufferout # remove ourselves
}

./testout 2>&1 | bufferout &! # run in background, disowned

When the job is done, the current prompt and input buffer will be removed and the entirety of the command's stdout and stderr will be printed.

Note that this assumes it will be run exactly once and removes itself afterwards. If you want to keep reusing this functionality in the same shell, remove the unhash -f line in TRAPUSR1.


This answer includes an improvement suggested by Clint Priest in the comments. Thanks!



Related Topics



Leave a reply



Submit