mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 07:28:01 -05:00
245 lines
9.7 KiB
R
245 lines
9.7 KiB
R
% Generated by roxygen2: do not edit by hand
|
|
% Please edit documentation in R/extended-task.R
|
|
\name{ExtendedTask}
|
|
\alias{ExtendedTask}
|
|
\title{Task or computation that proceeds in the background}
|
|
\description{
|
|
In normal Shiny reactive code, whenever an observer, calc, or
|
|
output is busy computing, it blocks the current session from receiving any
|
|
inputs or attempting to proceed with any other computation related to that
|
|
session.
|
|
|
|
The \code{ExtendedTask} class allows you to have an expensive operation that is
|
|
started by a reactive effect, and whose (eventual) results can be accessed
|
|
by a regular observer, calc, or output; but during the course of the
|
|
operation, the current session is completely unblocked, allowing the user
|
|
to continue using the rest of the app while the operation proceeds in the
|
|
background.
|
|
|
|
Note that each \code{ExtendedTask} object does not represent a \emph{single
|
|
invocation} of its long-running function. Rather, it's an object that is
|
|
used to invoke the function with different arguments, keeps track of
|
|
whether an invocation is in progress, and provides ways to get at the
|
|
current status or results of the operation. A single \code{ExtendedTask} object
|
|
does not permit overlapping invocations: if the \code{invoke()} method is called
|
|
before the previous \code{invoke()} is completed, the new invocation will not
|
|
begin until the previous invocation has completed.
|
|
}
|
|
\section{\code{ExtendedTask} versus asynchronous reactives}{
|
|
|
|
|
|
Shiny has long supported \href{https://rstudio.github.io/promises/articles/promises_06_shiny.html}{using \{promises\}}
|
|
to write asynchronous observers, calcs, or outputs. You may be wondering
|
|
what the differences are between those techniques and this class.
|
|
|
|
Asynchronous observers, calcs, and outputs are not--and have never
|
|
been--designed to let a user start a long-running operation, while keeping
|
|
that very same (browser) session responsive to other interactions. Instead,
|
|
they unblock other sessions, so you can take a long-running operation that
|
|
would normally bring the entire R process to a halt and limit the blocking
|
|
to just the session that started the operation. (For more details, see the
|
|
section on \href{https://rstudio.github.io/promises/articles/promises_06_shiny.html#the-flush-cycle}{"The Flush Cycle"}.)
|
|
|
|
\code{ExtendedTask}, on the other hand, invokes an asynchronous function (that
|
|
is, a function that quickly returns a promise) and allows even that very
|
|
session to immediately unblock and carry on with other user interactions.
|
|
}
|
|
|
|
\section{OpenTelemetry Integration}{
|
|
|
|
|
|
When an \code{ExtendedTask} is created, if OpenTelemetry tracing is enabled for
|
|
\code{"reactivity"} (see \code{\link[=withOtelCollect]{withOtelCollect()}}), the \code{ExtendedTask} will record
|
|
spans for each invocation of the task. The tracing level at \code{invoke()} time
|
|
does not affect whether spans are recorded; only the tracing level when
|
|
calling \code{ExtendedTask$new()} matters.
|
|
|
|
The OTel span will be named based on the label created from the variable the
|
|
\code{ExtendedTask} is assigned to. If no label can be determined, the span will
|
|
be named \verb{<anonymous>}. Similar to other Shiny OpenTelemetry spans, the span
|
|
will also include source reference attributes and session ID attributes.
|
|
|
|
\if{html}{\out{<div class="sourceCode r">}}\preformatted{withOtelCollect("all", \{
|
|
my_task <- ExtendedTask$new(function(...) \{ ... \})
|
|
\})
|
|
|
|
# Span recorded for this invocation: ExtendedTask my_task
|
|
my_task$invoke(...)
|
|
}\if{html}{\out{</div>}}
|
|
}
|
|
|
|
\examples{
|
|
\dontshow{if (rlang::is_interactive() && rlang::is_installed("mirai")) withAutoprint(\{ # examplesIf}
|
|
library(shiny)
|
|
library(bslib)
|
|
library(mirai)
|
|
|
|
# Set background processes for running tasks
|
|
daemons(1)
|
|
# Reset when the app is stopped
|
|
onStop(function() daemons(0))
|
|
|
|
ui <- page_fluid(
|
|
titlePanel("Extended Task Demo"),
|
|
p(
|
|
'Click the button below to perform a "calculation"',
|
|
"that takes a while to perform."
|
|
),
|
|
input_task_button("recalculate", "Recalculate"),
|
|
p(textOutput("result"))
|
|
)
|
|
|
|
server <- function(input, output) {
|
|
rand_task <- ExtendedTask$new(function() {
|
|
mirai(
|
|
{
|
|
# Slow operation goes here
|
|
Sys.sleep(2)
|
|
sample(1:100, 1)
|
|
}
|
|
)
|
|
})
|
|
|
|
# Make button state reflect task.
|
|
# If using R >=4.1, you can do this instead:
|
|
# rand_task <- ExtendedTask$new(...) |> bind_task_button("recalculate")
|
|
bind_task_button(rand_task, "recalculate")
|
|
|
|
observeEvent(input$recalculate, {
|
|
# Invoke the extended in an observer
|
|
rand_task$invoke()
|
|
})
|
|
|
|
output$result <- renderText({
|
|
# React to updated results when the task completes
|
|
number <- rand_task$result()
|
|
paste0("Your number is ", number, ".")
|
|
})
|
|
}
|
|
|
|
shinyApp(ui, server)
|
|
\dontshow{\}) # examplesIf}
|
|
}
|
|
\section{Methods}{
|
|
\subsection{Public methods}{
|
|
\itemize{
|
|
\item \href{#method-ExtendedTask-new}{\code{ExtendedTask$new()}}
|
|
\item \href{#method-ExtendedTask-invoke}{\code{ExtendedTask$invoke()}}
|
|
\item \href{#method-ExtendedTask-status}{\code{ExtendedTask$status()}}
|
|
\item \href{#method-ExtendedTask-result}{\code{ExtendedTask$result()}}
|
|
}
|
|
}
|
|
\if{html}{\out{<hr>}}
|
|
\if{html}{\out{<a id="method-ExtendedTask-new"></a>}}
|
|
\if{latex}{\out{\hypertarget{method-ExtendedTask-new}{}}}
|
|
\subsection{Method \code{new()}}{
|
|
Creates a new \code{ExtendedTask} object. \code{ExtendedTask} should generally be
|
|
created either at the top of a server function, or at the top of a module
|
|
server function.
|
|
\subsection{Usage}{
|
|
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$new(func)}\if{html}{\out{</div>}}
|
|
}
|
|
|
|
\subsection{Arguments}{
|
|
\if{html}{\out{<div class="arguments">}}
|
|
\describe{
|
|
\item{\code{func}}{The long-running operation to execute. This should be an
|
|
asynchronous function, meaning, it should use the
|
|
\href{https://rstudio.github.io/promises/}{\{promises\}} package, most
|
|
likely in conjunction with the
|
|
\href{https://mirai.r-lib.org}{\{mirai\}} or
|
|
\href{https://rstudio.github.io/promises/articles/promises_04_futures.html}{\{future\}}
|
|
package. (In short, the return value of \code{func} should be a
|
|
\code{\link[mirai:mirai]{mirai}}, \code{\link[future:future]{Future}}, \code{promise},
|
|
or something else that \code{\link[promises:is.promise]{promises::as.promise()}} understands.)
|
|
|
|
It's also important that this logic does not read from any
|
|
reactive inputs/sources, as inputs may change after the function is
|
|
invoked; instead, if the function needs to access reactive inputs, it
|
|
should take parameters and the caller of the \code{invoke()} method should
|
|
read reactive inputs and pass them as arguments.}
|
|
}
|
|
\if{html}{\out{</div>}}
|
|
}
|
|
}
|
|
\if{html}{\out{<hr>}}
|
|
\if{html}{\out{<a id="method-ExtendedTask-invoke"></a>}}
|
|
\if{latex}{\out{\hypertarget{method-ExtendedTask-invoke}{}}}
|
|
\subsection{Method \code{invoke()}}{
|
|
Starts executing the long-running operation. If this \code{ExtendedTask} is
|
|
already running (meaning, a previous call to \code{invoke()} is not yet
|
|
complete) then enqueues this invocation until after the current
|
|
invocation, and any already-enqueued invocation, completes.
|
|
\subsection{Usage}{
|
|
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$invoke(...)}\if{html}{\out{</div>}}
|
|
}
|
|
|
|
\subsection{Arguments}{
|
|
\if{html}{\out{<div class="arguments">}}
|
|
\describe{
|
|
\item{\code{...}}{Parameters to use for this invocation of the underlying
|
|
function. If reactive inputs are needed by the underlying function,
|
|
they should be read by the caller of \code{invoke} and passed in as
|
|
arguments.}
|
|
}
|
|
\if{html}{\out{</div>}}
|
|
}
|
|
}
|
|
\if{html}{\out{<hr>}}
|
|
\if{html}{\out{<a id="method-ExtendedTask-status"></a>}}
|
|
\if{latex}{\out{\hypertarget{method-ExtendedTask-status}{}}}
|
|
\subsection{Method \code{status()}}{
|
|
This is a reactive read that invalidates the caller when the task's
|
|
status changes.
|
|
|
|
Returns one of the following values:
|
|
\itemize{
|
|
\item \code{"initial"}: This \code{ExtendedTask} has not yet been invoked
|
|
\item \code{"running"}: An invocation is currently running
|
|
\item \code{"success"}: An invocation completed successfully, and a value can be
|
|
retrieved via the \code{result()} method
|
|
\item \code{"error"}: An invocation completed with an error, which will be
|
|
re-thrown if you call the \code{result()} method
|
|
}
|
|
\subsection{Usage}{
|
|
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$status()}\if{html}{\out{</div>}}
|
|
}
|
|
|
|
}
|
|
\if{html}{\out{<hr>}}
|
|
\if{html}{\out{<a id="method-ExtendedTask-result"></a>}}
|
|
\if{latex}{\out{\hypertarget{method-ExtendedTask-result}{}}}
|
|
\subsection{Method \code{result()}}{
|
|
Attempts to read the results of the most recent invocation. This is a
|
|
reactive read that invalidates as the task's status changes.
|
|
|
|
The actual behavior differs greatly depending on the current status of
|
|
the task:
|
|
\itemize{
|
|
\item \code{"initial"}: Throws a silent error (like \code{\link[=req]{req(FALSE)}}). If
|
|
this happens during output rendering, the output will be blanked out.
|
|
\item \code{"running"}: Throws a special silent error that, if it happens during
|
|
output rendering, makes the output appear "in progress" until further
|
|
notice.
|
|
\item \code{"success"}: Returns the return value of the most recent invocation.
|
|
\item \code{"error"}: Throws whatever error was thrown by the most recent
|
|
invocation.
|
|
}
|
|
|
|
This method is intended to be called fairly naively by any output or
|
|
reactive expression that cares about the output--you just have to be
|
|
aware that if the result isn't ready for whatever reason, processing will
|
|
stop in much the same way as \code{req(FALSE)} does, but when the result is
|
|
ready you'll get invalidated, and when you run again the result should be
|
|
there.
|
|
|
|
Note that the \code{result()} method is generally not meant to be used with
|
|
\code{\link[=observeEvent]{observeEvent()}}, \code{\link[=eventReactive]{eventReactive()}}, \code{\link[=bindEvent]{bindEvent()}}, or \code{\link[=isolate]{isolate()}} as the
|
|
invalidation will be ignored.
|
|
\subsection{Usage}{
|
|
\if{html}{\out{<div class="r">}}\preformatted{ExtendedTask$result()}\if{html}{\out{</div>}}
|
|
}
|
|
|
|
}
|
|
}
|