2

I want to keep reflashing until 10:05, after 10:05 I got the tplus0_dt and stop to processing invalidaterLater().

Before 10:00, tplus0_dt doesn't exist, so I need to keep reflashing until 10:05. After 10:05, it is no need to refalsh, and when the tplus0_dt becomes very lage the invalidaterLater() will effects the displaying of table1, the screen and plots go GRAY every 1 seconds so it looks like the page is dead while the updating occurs.

So how can I do to stop processing the invalidateLater() and keep showing the data after 10:05? Thanks for help! My example code were below.

require(shiny)
require(data.table)
app <- shinyApp(
    server = function(input, output, session){
            get_tplus0_data <- reactive({
                    time <- substr(as.character(Sys.time()), 12, 16)
                    invalidateLater(1000)
                    if(time >= "10:05"){
                            # tplus0_dt<-data.table(read.csv("/data/df_highest_after_signal.csv",header = T, sep = ",", stringsAsFactors = F)) 
                            tplus0_dt<- data.table(a = c(1, 2, 3, 4), b = c(3, 4, 5, 8)) 
                            return(tplus0_dt)
                    }

            })
            output$table1 <- renderTable({get_tplus0_data()})
    },
    ui = fluidPage( tableOutput("table1")  )
)

runApp(app)
Leah210
  • 109
  • 1
  • 1
  • 8

2 Answers2

8

Although you'd never realize it from the Shiny documentation, invalidateLater() actually only returns to your reactive once. The reason it seems to return repeatedly is that on each trip the invalidateLater() function gets run again.

So the solution is to use a conditional around the function so that you don't keep repeatedly calling it:

if(runMeAgain) {
   invalidateLater(n)
}

runMeAgain = TRUE   # your reactive re-runs every n milliseconds
runMeAgain = FALSE  # your reactive does not re-run

Also note that:

  • invalidateLater() is non-blocking (other code can run while you wait)
  • invalidateLater() doesn't stop the rest of the reactive from running. If you want to stop the reactive at that point in the code, put a return() after invalidateLater()
  • invalidateLater() gets isolated() inside an observeEvent() or eventReactive() and consequently doesn't work; you have to use observe() or reactive(). It might also work inside a render function, but I haven't ever had a reason to try that.

In terms of the original question, the reactive should look like this:

get_tplus0_data <- reactive({
    time <- substr(as.character(Sys.time()), 12, 16)
    if(time >= "10:05"){
        tplus0_dt<- data.table(a = c(1, 2, 3, 4), b = c(3, 4, 5, 8)) 
        return(tplus0_dt)
    } else {
        invalidateLater(1000)
        return()
    }
})
3D0G
  • 797
  • 8
  • 15
  • Could you provde an example with this implementation? – Patrik_P Jul 02 '20 at 07:59
  • 1
    The answer has been edited to add an example in terms of the original question. – 3D0G Jul 03 '20 at 15:40
  • Thnx, so what actually happens, is that the invalidation is consistently only triggered between midnight and 10:05, right? – Patrik_P Jul 06 '20 at 10:29
  • Well, something else has to cause the reactive to run the first time. There is nothing magical about midnight that's going to cause the reactive to run. But if something runs the reactive a first time, and if the time variable is less than 10:05, the reactive will rerun every second; if the time variable is equal to or greater than 10:05, it does not rerun at all - ever again. (Unless something else reruns it.) – 3D0G Jul 07 '20 at 14:55
  • If what you actually want is to have the reactive run every second forever, make the invalidateLater() the first thing the reactive does. But that's essentially what the original question does, so if that's not doing what you want, the problem is with the rest of the code, not with invalidateLater(). – 3D0G Jul 07 '20 at 15:11
  • No, fits, not midnight seems magical, but the coditional stament, that keeps being checked when the invalidation statement lives inside. That is bewildering me, the following is what I mean: Lets say that app runs the whole day long 7 days a week. Is is still behaving as described above? Or is the app bahaviour only reacting on runtime time? – Patrik_P Jul 08 '20 at 11:58
  • 1
    The key point is that invalidateLater() causes the reactive it's inside to run again ONCE. If invalidateLater() has a position in the code that causes it to execute every time the reactive runs, the reactive will run continuously. But if you use a conditional to branch around the invalidateLater() so that it isn't executed, the reactive won't run again. – 3D0G Jul 09 '20 at 12:23
  • And if the reactive is part of an app that runs continuously, and if the app calls the reactive to start it, the reactive will effectively run continuously inside the app, UNLESS you use a conditional to branch around execution of invalidateLater(). If the reactive doesn't execute the invalidateLater() function, then it becomes like any other reactive and stops until something else runs it again. – 3D0G Jul 09 '20 at 18:45
5

How about you override the function to your needs?

If you enter invalidateLaterNew in the console, the code of the function will be printed.

To overwrite a function within a package this post will help: Override a function that is imported in a namespace

Then you will have to consider that the functions .getReactiveEnvironment() and timerCallbacks() are not accessible outside the namespace. But you can call them like this: shiny:::.getReactiveEnvironment()

Bring it together:

You add an additional parameter (e.g. update), which will enable you to stop the invalideLater() whenever you want.

invalidateLaterNew <- function (millis, session = getDefaultReactiveDomain(), update = TRUE) 
{
  if(update){
    ctx <- shiny:::.getReactiveEnvironment()$currentContext()
    shiny:::timerCallbacks$schedule(millis, function() {
      if (!is.null(session) && session$isClosed()) {
        return(invisible())
      }
      ctx$invalidate()
    })
    invisible()
  }
}

unlockBinding("invalidateLater", as.environment("package:shiny"))
assign("invalidateLater", invalidateLaterNew, "package:shiny")

Example:

I used the example given in ?invalidateLater to demonstrate the effect: (invalidateLater will stop when input$nis bigger than 800. So you can adapt this example to your time restriction). I decided not to use your time restriction example as it wouldnt be that handy to test ;)

ui <- fluidPage(
  sliderInput("n", "Number of observations", 2, 1000, 500),
  plotOutput("plot")
)

server <- function(input, output, session) {

  observe({
    # Re-execute this reactive expression after 1000 milliseconds
    invalidateLater(1000, session, input$n < 800)
    # Do something each time this is invalidated.
    # The isolate() makes this observer _not_ get invalidated and re-executed
    # when input$n changes.
    print(paste("The value of input$n is", isolate(input$n)))
  })

  # Generate a new histogram at timed intervals, but not when
  # input$n changes.
  output$plot <- renderPlot({
    # Re-execute this reactive expression after 2000 milliseconds
    invalidateLater(2000, session, input$n < 800)
    hist(rnorm(isolate(input$n)))
  })
}

shinyApp(ui, server)
Community
  • 1
  • 1
Tonio Liebrand
  • 17,189
  • 4
  • 39
  • 59