Tenho um aplicativo simples e brilhante onde um conjunto de dados é criado em uma reactive()
chamada e depois plotado. Como a criação dos dados pode demorar um pouco, quero que ele seja chamado de assíncrono para que a sessão possa ser usada de outra forma. Para isso, quero usar shiny::ExtendedTask()
with mirai
.
Os documentos oficiais têm exemplos em que o task$invoke(...)
é acionado por um botão (por exemplo, dentro de um shiny::observeEvent()
), mas não há exemplos de como posso estruturar isso se minha função for acionada dentro de um reactive()
.
Observe que meu caso de uso real reutiliza os dados resultantes em alguns gráficos na página específica.
Aplicativo de sincronização MWE
O seguinte é um MWE para um aplicativo de bloqueio
library(shiny)
library(bslib)
create_data <- function(n) {
Sys.sleep(2)
data.frame(x = seq(n), y = rnorm(n))
}
ui <- page_fillable(
card(
card_header(span("To show reactive state, here is the current time ",
textOutput("current_time", inline = TRUE))),
plotOutput("myplot")
)
)
server <- function(input, output, session) {
# note this reactive here should be put into an ExtendedTask
data <- reactive({
create_data(100)
})
output$myplot <- renderPlot({
d <- data()
plot(d$x, d$y)
})
# to show if the session is free: show current time
output$current_time <- renderText({
invalidateLater(1000, session)
format(Sys.time(), "%H:%M:%S")
})
}
shinyApp(ui, server)
Observe como a hora não é mostrada imediatamente porque a create_data()
função bloqueia a sessão.
Tentativa de um aplicativo assíncrono
Para mostrar quando os eventos são acionados, movo o conteúdo para uma segunda página da barra de navegação. Portanto, espero que as tarefas sejam acionadas quando os dados forem necessários, pois a página é exibida (por exemplo, reactive()
e não observe()
o comportamento).
Minha tentativa malsucedida é então a seguinte (observe como eu aciono o task$invoke
com um observe()
este é o erro, mas não sei como eu acionaria isso com um reactive()
ou de outra forma.
library(shiny)
library(bslib)
library(mirai)
create_data <- function(n) {
Sys.sleep(2)
data.frame(x = seq(n), y = rnorm(n))
}
# small helper function for logging
flog <- function(m) cat(sprintf("INFO [%s] | %s\n", format(Sys.time(), "%Y-%m-%d %H:%M:%OS3"), m))
ui <- page_navbar(
nav_panel("Empty Default"),
nav_panel(
"Same as Before",
card_header(span("To show reactive state, here is the current time ",
textOutput("current_time", inline = TRUE))),
plotOutput("myplot")
)
)
server <- function(input, output, session) {
# create the task
task <- ExtendedTask$new(function(...) mirai(fun(n = n), fun = create_data, ...))
# this is the error here: the observe is not triggered by the rendered plot but by observe => fires immediately
observe({
flog("Task Invoke")
task$invoke(n = 100)
flog("Task Invoke Done")
})
output$myplot <- renderPlot({
flog("Task Result")
data <- task$result()
flog("Task Result Done")
plot(data$x, data$y)
})
# to show if the session is free: show current time
output$current_time <- renderText({
invalidateLater(1000, session)
format(Sys.time(), "%H:%M:%S")
})
}
shinyApp(ui, server)
Você pode colocar a invocação da tarefa em um
reactive()
método próprio para acioná-la lentamente. O resultado é armazenado em cache e nunca é invalidado, portanto, será executado apenas uma vez. Depois, basta depender dessa reatividade na saída:Que produz: