Introduces an asynchronous boundary at the current stage in the asynchronous processing pipeline, making processing to jump on the given Scheduler (until the next async boundary).
Introduces an asynchronous boundary at the current stage in the asynchronous processing pipeline, making processing to jump on the given Scheduler (until the next async boundary).
Consider the following example:
import monix.execution.Scheduler val io = Scheduler.io() val source = Task(1).executeOn(io).map(_ + 1)
That task is being forced to execute on the io
scheduler,
including the map
transformation that follows after
executeOn
. But what if we want to jump with the execution
run-loop on another scheduler for the following transformations?
Then we can do:
import monix.execution.Scheduler.global source.asyncBoundary(global).map(_ + 2)
In this sample, whatever gets evaluated by the source
will
happen on the io
scheduler, however the asyncBoundary
call
will make all subsequent operations to happen on the specified
global
scheduler.
is the scheduler triggering the asynchronous boundary
Introduces an asynchronous boundary at the current stage in the asynchronous processing pipeline.
Introduces an asynchronous boundary at the current stage in the asynchronous processing pipeline.
Consider the following example:
import monix.execution.Scheduler val io = Scheduler.io() val source = Task(1).executeOn(io).map(_ + 1)
That task is being forced to execute on the io
scheduler,
including the map
transformation that follows after
executeOn
. But what if we want to jump with the execution
run-loop on the default scheduler for the following
transformations?
Then we can do:
source.asyncBoundary.map(_ + 2)
In this sample, whatever gets evaluated by the source
will
happen on the io
scheduler, however the asyncBoundary
call
will make all subsequent operations to happen on the default
scheduler.
Creates a new Task that will expose any triggered error from the source.
Returns a new task that is cancelable.
Returns a new task that is cancelable.
Normally Monix Tasks have these characteristics:
flatMap
chains are not cancelable by defaultThis operation returns a task that has Task.Options.autoCancelableRunLoops enabled upon evaluation, thus being equivalent with:
task.executeWithOptions(_.enableAutoCancelableRunLoops)
What this does is two-fold:
flatMap
chains become cancelable on async boundaries, which works in
combination with BatchedExecution
that's enabled by default (injected by Scheduler,
but can also be changed with executeWithModel)For example this is a function that calculates the n-th Fibonacci element:
def fib(n: Int): Task[Long] = { def loop(n: Int, a: Long, b: Long): Task[Long] = Task.suspend { if (n > 0) loop(n - 1, b, a + b) else Task.now(a) } loop(n, 0, 1).cancelable }
Normally this isn't cancelable and it might take a long time, but
by calling autoCancelable
on the result, we ensure that when cancellation
is observed, at async boundaries, the loop will stop with the task
becoming a non-terminating one.
This operation represents the opposite of uncancelable. And note
that it works even for tasks that are uncancelable / atomic, because
it blocks the rest of the flatMap
loop from executing, functioning
like a sort of cancellation boundary:
Task.evalAsync(println("Hello ...")) .cancelable .flatMap(_ => Task.eval(println("World!")))
Normally Task.apply does not yield a cancelable task, but by applying
the autoCancelable
transformation to it, the println
will execute,
but not the subsequent flatMap
operation.
Returns a task that treats the source task as the acquisition of a resource,
which is then exploited by the use
function and then released
.
Returns a task that treats the source task as the acquisition of a resource,
which is then exploited by the use
function and then released
.
The bracket
operation is the equivalent of the
try {} catch {} finally {}
statements from mainstream languages.
The bracket
operation installs the necessary exception handler to release
the resource in the event of an exception being raised during the computation,
or in case of cancellation.
If an exception is raised, then bracket
will re-raise the exception
after performing the release
. If the resulting task gets cancelled,
then bracket
will still perform the release
, but the yielded task
will be non-terminating (equivalent with Task.never).
Example:
import java.io._ def readFile(file: File): Task[String] = { // Opening a file handle for reading text val acquire = Task.eval(new BufferedReader( new InputStreamReader(new FileInputStream(file), "utf-8") )) acquire.bracket { in => // Usage part Task.eval { // Yes, ugly Java, non-FP loop; // side-effects are suspended though var line: String = null val buff = new StringBuilder() do { line = in.readLine() if (line != null) buff.append(line) } while (line != null) buff.toString() } } { in => // The release part Task.eval(in.close()) } }
Note that in case of cancellation the underlying implementation cannot
guarantee that the computation described by use
doesn't end up
executed concurrently with the computation from release
. In the example
above that ugly Java loop might end up reading from a BufferedReader
that is already closed due to the task being cancelled, thus triggering
an error in the background with nowhere to go but in
Scheduler.reportFailure.
In this particular example, given that we are just reading from a file, it doesn't matter. But in other cases it might matter, as concurrency on top of the JVM when dealing with I/O might lead to corrupted data.
For those cases you might want to do synchronization (e.g. usage of locks and semaphores) and you might want to use bracketE, the version that allows you to differentiate between normal termination and cancellation.
NOTE on error handling: one big
difference versus try {} finally {}
is that, in case
both the release
function and the use
function throws,
the error raised by use
gets signaled and the error
raised by release
gets reported with System.err
for
Coeval or with
Scheduler.reportFailure
for Task.
For example:
Task.evalAsync("resource").bracket { _ => // use Task.raiseError(new RuntimeException("Foo")) } { _ => // release Task.raiseError(new RuntimeException("Bar")) }
In this case the error signaled downstream is "Foo"
,
while the "Bar"
error gets reported. This is consistent
with the behavior of Haskell's bracket
operation and NOT
with try {} finally {}
from Scala, Java or JavaScript.
is a function that evaluates the resource yielded by the source,
yielding a result that will get generated by the task returned
by this bracket
function
is a function that gets called after use
terminates,
either normally or in error, or if it gets cancelled, receiving
as input the resource that needs to be released
bracketCase and bracketE
Returns a new task that treats the source task as the
acquisition of a resource, which is then exploited by the use
function and then released
, with the possibility of
distinguishing between normal termination and cancelation, such
that an appropriate release of resources can be executed.
Returns a new task that treats the source task as the
acquisition of a resource, which is then exploited by the use
function and then released
, with the possibility of
distinguishing between normal termination and cancelation, such
that an appropriate release of resources can be executed.
The bracketCase
operation is the equivalent of
try {} catch {} finally {}
statements from mainstream languages
when used for the acquisition and release of resources.
The bracketCase
operation installs the necessary exception handler
to release the resource in the event of an exception being raised
during the computation, or in case of cancelation.
In comparison with the simpler bracket version, this one
allows the caller to differentiate between normal termination,
termination in error and cancelation via an ExitCase
parameter.
is a function that evaluates the resource yielded by the source, yielding a result that will get generated by this function on evaluation
is a function that gets called after use
terminates, either normally or in error, or if it gets
canceled, receiving as input the resource that needs that
needs release, along with the result of use
(cancelation, error or successful result)
Returns a task that treats the source task as the acquisition of a resource,
which is then exploited by the use
function and then released
, with
the possibility of distinguishing between normal termination and cancellation,
such that an appropriate release of resources can be executed.
Returns a task that treats the source task as the acquisition of a resource,
which is then exploited by the use
function and then released
, with
the possibility of distinguishing between normal termination and cancellation,
such that an appropriate release of resources can be executed.
The bracketE
operation is the equivalent of try {} catch {} finally {}
statements from mainstream languages.
The bracketE
operation installs the necessary exception handler to release
the resource in the event of an exception being raised during the computation,
or in case of cancellation.
In comparison with the simpler bracket version, this one allows the caller to differentiate between normal termination and cancellation.
The release
function receives as input:
Left(None)
in case of cancellationLeft(Some(error))
in case use
terminated with an errorRight(b)
in case of successNOTE on error handling: one big
difference versus try {} finally {}
is that, in case
both the release
function and the use
function throws,
the error raised by use
gets signaled and the error
raised by release
gets reported with System.err
for
Coeval or with
Scheduler.reportFailure
for Task.
For example:
Task.evalAsync("resource").bracket { _ => // use Task.raiseError(new RuntimeException("Foo")) } { _ => // release Task.raiseError(new RuntimeException("Bar")) }
In this case the error signaled downstream is "Foo"
,
while the "Bar"
error gets reported. This is consistent
with the behavior of Haskell's bracket
operation and NOT
with try {} finally {}
from Scala, Java or JavaScript.
is a function that evaluates the resource yielded by the source, yielding a result that will get generated by this function on evaluation
is a function that gets called after use
terminates,
either normally or in error, or if it gets cancelled, receiving
as input the resource that needs that needs release, along with
the result of use
(cancellation, error or successful result)
bracket and bracketCase
Returns a task that waits for the specified timespan
before
executing and mirroring the result of the source.
Returns a task that waits for the specified timespan
before
executing and mirroring the result of the source.
In this example we're printing to standard output, but before doing that we're introducing a 3 seconds delay:
Task(println("Hello!")) .delayExecution(3.seconds)
This operation is also equivalent with:
Task.sleep(timespan).flatMap(_ => task)
See Task.sleep for the operation that describes the effect and Task.delayResult for the version that evaluates the task on time, but delays the signaling of the result.
is the time span to wait before triggering the evaluation of the task
Returns a task that executes the source immediately on runAsync
,
but before emitting the onSuccess
result for the specified
duration.
Returns a task that executes the source immediately on runAsync
,
but before emitting the onSuccess
result for the specified
duration.
Note that if an error happens, then it is streamed immediately with no delay.
See delayExecution for delaying the evaluation of the task with the specified duration. The delayResult operation is effectively equivalent with:
task.flatMap(a => Task.now(a).delayExecution(timespan))
Or if we are to use the Task.sleep describing just the effect, this operation is equivalent with:
task.flatMap(a => Task.sleep(timespan).map(_ => a))
Thus in this example 3 seconds will pass before the result is being generated by the source, plus another 5 seconds before it is finally emitted:
Task.eval(1 + 1) .delayExecution(3.seconds) .delayResult(5.seconds)
is the time span to sleep before signaling the result, but after the evaluation of the source
Dematerializes the source's result from a Try
.
Returns a new Task
that will mirror the source, but that will
execute the given callback
if the task gets canceled before
completion.
Returns a new Task
that will mirror the source, but that will
execute the given callback
if the task gets canceled before
completion.
This only works for premature cancellation. See doOnFinish for triggering callbacks when the source finishes.
is the callback to execute if the task gets canceled prematurely
Returns a new Task
in which f
is scheduled to be run on
completion.
Returns a new Task
in which f
is scheduled to be run on
completion. This would typically be used to release any
resources acquired by this Task
.
The returned Task
completes when both the source and the task
returned by f
complete.
NOTE: The given function is only called when the task is complete. However the function does not get called if the task gets canceled. Cancellation is a process that's concurrent with the execution of a task and hence needs special handling.
See doOnCancel for specifying a callback to call on canceling a task.
Mirrors the given source Task
, but upon execution ensure
that evaluation forks into a separate (logical) thread.
Mirrors the given source Task
, but upon execution ensure
that evaluation forks into a separate (logical) thread.
The Scheduler used will be the one that is used to start the run-loop in .runAsync.
This operation is equivalent with:
Task.shift.flatMap(_ => task) // ... or ... import cats.syntax.all._ Task.shift.followedBy(task)
The Scheduler used for scheduling
the async boundary will be the default, meaning the one used to
start the run-loop in runAsync
.
Overrides the default Scheduler,
possibly forcing an asynchronous boundary before execution
(if forceAsync
is set to true
, the default).
Overrides the default Scheduler,
possibly forcing an asynchronous boundary before execution
(if forceAsync
is set to true
, the default).
When a Task
is executed with .runAsync,
it needs a Scheduler
, which is going to be injected in all
asynchronous tasks processed within the flatMap
chain,
a Scheduler
that is used to manage asynchronous boundaries
and delayed execution.
This scheduler passed in runAsync
is said to be the "default"
and executeOn
overrides that default.
import monix.execution.Scheduler import java.io.{BufferedReader, FileInputStream, InputStreamReader} /** Reads the contents of a file using blocking I/O. */ def readFile(path: String): Task[String] = Task.eval { val in = new BufferedReader( new InputStreamReader(new FileInputStream(path), "utf-8")) val buffer = new StringBuffer() var line: String = null do { line = in.readLine() if (line != null) buffer.append(line) } while (line != null) buffer.toString } // Building a Scheduler meant for blocking I/O val io = Scheduler.io() // Building the Task reference, specifying that `io` should be // injected as the Scheduler for managing async boundaries readFile("path/to/file").executeOn(io, forceAsync = true)
In this example we are using Task.eval, which executes the
given thunk
immediately (on the current thread and call stack).
By calling executeOn(io)
, we are ensuring that the used
Scheduler
(injected in async tasks)
will be io
, a Scheduler
that we intend to use for blocking
I/O actions. And we are also forcing an asynchronous boundary
right before execution, by passing the forceAsync
parameter as
true
(which happens to be the default value).
Thus, for our described function that reads files using Java's
blocking I/O APIs, we are ensuring that execution is entirely
managed by an io
scheduler, executing that logic on a thread
pool meant for blocking I/O actions.
Note that in case forceAsync = false
, then the invocation will
not introduce any async boundaries of its own and will not
ensure that execution will actually happen on the given
Scheduler
, that depending of the implementation of the Task
.
For example:
Task.eval("Hello, " + "World!") .executeOn(io, forceAsync = false)
The evaluation of this task will probably happen immediately (depending on the configured ExecutionModel) and the given scheduler will probably not be used at all.
However in case we would use Task.apply, which ensures
that execution of the provided thunk will be async, then
by using executeOn
we'll indeed get a logical fork on
the io
scheduler:
Task("Hello, " + "World!").executeOn(io, forceAsync = false)
Also note that overriding the "default" scheduler can only happen once, because it's only the "default" that can be overridden.
Something like this won't have the desired effect:
val io1 = Scheduler.io() val io2 = Scheduler.io() task.executeOn(io1).executeOn(io2)
In this example the implementation of task
will receive
the reference to io1
and will use it on evaluation, while
the second invocation of executeOn
will create an unnecessary
async boundary (if forceAsync = true
) or be basically a
costly no-op. This might be confusing but consider the
equivalence to these functions:
import scala.concurrent.ExecutionContext val io1 = Scheduler.io() val io2 = Scheduler.io() def sayHello(ec: ExecutionContext): Unit = ec.execute(new Runnable { def run() = println("Hello!") }) def sayHello2(ec: ExecutionContext): Unit = // Overriding the default `ec`! sayHello(io1) def sayHello3(ec: ExecutionContext): Unit = // Overriding the default no longer has the desired effect // because sayHello2 is ignoring it! sayHello2(io2)
is the Scheduler to use
for overriding the default scheduler and for forcing
an asynchronous boundary if forceAsync
is true
indicates whether an asynchronous boundary
should be forced right before the evaluation of the
Task
, managed by the provided Scheduler
a new Task
that mirrors the source on evaluation,
but that uses the provided scheduler for overriding
the default and possibly force an extra asynchronous
boundary on execution
Returns a new task that will execute the source with a different ExecutionModel.
Returns a new task that will execute the source with a different ExecutionModel.
This allows fine-tuning the options injected by the scheduler locally. Example:
import monix.execution.ExecutionModel.AlwaysAsyncExecution
task.executeWithModel(AlwaysAsyncExecution)
is the
ExecutionModel
with which the source will get evaluated on runAsync
Returns a new task that will execute the source with a different set of Options.
Returns a new task that will execute the source with a different set of Options.
This allows fine-tuning the default options. Example:
task.executeWithOptions(_.enableAutoCancelableRunLoops)
is a function that takes the source's current set of
options and returns a modified set of
options that will be used to execute the source
upon runAsync
Returns a failed projection of this task.
Returns a failed projection of this task.
The failed projection is a Task
holding a value of type Throwable
,
emitting the error yielded by the source, in case the source fails,
otherwise if the source succeeds the result will fail with a
NoSuchElementException
.
Creates a new Task by applying a function to the successful result of the source Task, and returns a task equivalent to the result of the function.
Given a source Task that emits another Task, this function flattens the result, returning a Task equivalent to the emitted Task by the source.
Triggers the evaluation of the source, executing the given function for the generated element.
Triggers the evaluation of the source, executing the given function for the generated element.
The application of this function has strict behavior, as the task is immediately executed.
Exceptions in f
are reported using provided (implicit) Scheduler
Returns a new task that upon evaluation will execute the given
function for the generated element, transforming the source into
a Task[Unit]
.
Returns a new task that upon evaluation will execute the given
function for the generated element, transforming the source into
a Task[Unit]
.
Similar in spirit with normal foreach, but lazy, as obviously nothing gets executed at this point.
Start asynchronous execution of the source suspended in the Task
context,
running it in the background and discarding the result.
Start asynchronous execution of the source suspended in the Task
context,
running it in the background and discarding the result.
Similar to start after mapping result to Unit. Below law holds:
task.forkAndForget <-> task.start.map(_ => ())
Returns a new Task
that repeatedly executes the source as long
as it continues to succeed.
Returns a new Task
that repeatedly executes the source as long
as it continues to succeed. It never produces a terminal value.
Example:
import scala.concurrent.duration._ Task.eval(println("Tick!")) .delayExecution(1.second) .loopForever
Returns a new Task
that applies the mapping function to
the element emitted by the source.
Returns a new Task
that applies the mapping function to
the element emitted by the source.
Can be used for specifying a (lazy) transformation to the result of the source.
This equivalence with flatMap always holds:
scala
fa.map(f) <-> fa.flatMap(x => Task.pure(f(x)))
Creates a new Task that will expose any triggered error from the source.
Memoizes (caches) the result of the source task and reuses it on
subsequent invocations of runAsync
.
Memoizes (caches) the result of the source task and reuses it on
subsequent invocations of runAsync
.
The resulting task will be idempotent, meaning that evaluating the resulting task multiple times will have the same effect as evaluating it once.
Cancellation — a memoized task will mirror the behavior of the source on cancellation. This means that:
Depending on use-case, there are two ways to ensure no surprises:
Example:
import scala.concurrent.CancellationException val source = Task(1).delayExecution(5.seconds) // Option 1: trigger error on cancellation val err = new CancellationException val cached1 = source.onCancelRaiseError(err).memoize // Option 2: make it uninterruptible val cached2 = source.uncancelable.memoize
When using onCancelRaiseError like in the example above, the
behavior of memoize
is to cache the error. If you want the ability
to retry errors until a successful value happens, see memoizeOnSuccess.
UNSAFE — this operation allocates a shared, mutable reference, which can break in certain cases referential transparency, even if this operation guarantees idempotency (i.e. referential transparency implies idempotency, but idempotency does not imply referential transparency).
The allocation of a mutable reference is known to be a side effect, thus breaking referential transparency, even if calling this method does not trigger the evaluation of side effects suspended by the source.
Use with care. Sometimes it's easier to just keep a shared, memoized reference to some connection, but keep in mind it might be better to pass such a reference around as a parameter.
a Task
that can be used to wait for the memoized value
memoizeOnSuccess for a version that only caches successful results
Memoizes (cache) the successful result of the source task
and reuses it on subsequent invocations of runAsync
.
Memoizes (cache) the successful result of the source task
and reuses it on subsequent invocations of runAsync
.
Thrown exceptions are not cached.
The resulting task will be idempotent, but only if the result is successful.
Cancellation — a memoized task will mirror the behavior of the source on cancellation. This means that:
Depending on use-case, there are two ways to ensure no surprises:
Example:
import scala.concurrent.CancellationException val source = Task(1).delayExecution(5.seconds) // Option 1: trigger error on cancellation val err = new CancellationException val cached1 = source.onCancelRaiseError(err).memoizeOnSuccess // Option 2: make it uninterruptible val cached2 = source.uncancelable.memoizeOnSuccess
When using onCancelRaiseError like in the example above, the
behavior of memoizeOnSuccess
is to retry the source on subsequent
invocations. Use memoize if that's not the desired behavior.
UNSAFE — this operation allocates a shared, mutable reference, which can break in certain cases referential transparency, even if this operation guarantees idempotency (i.e. referential transparency implies idempotency, but idempotency does not imply referential transparency).
The allocation of a mutable reference is known to be a side effect, thus breaking referential transparency, even if calling this method does not trigger the evaluation of side effects suspended by the source.
Use with care. Sometimes it's easier to just keep a shared, memoized reference to some connection, but keep in mind it might be better to pass such a reference around as a parameter.
a Task
that can be used to wait for the memoized value
memoize for a version that caches both successful results and failures
Returns a new task that mirrors the source task for normal termination, but that triggers the given error on cancellation.
Returns a new task that mirrors the source task for normal termination, but that triggers the given error on cancellation.
Normally tasks that are cancelled become non-terminating. Here's an example of a cancelable task:
val tenSecs = Task.sleep(10) val task = tenSecs.fork.flatMap { fa => // Triggering pure cancellation, then trying to get its result fa.cancel.flatMap(_ => fa) } task.timeout(10.seconds).runAsync //=> throws TimeoutException
In general you can expect cancelable tasks to become non-terminating on cancellation.
This onCancelRaiseError
operator transforms a task that would yield
Task.never on cancellation into one that yields Task.raiseError.
Example:
import java.util.concurrent.CancellationException val tenSecs = Task.sleep(10.seconds) .onCancelRaiseError(new CancellationException) val task = tenSecs.fork.flatMap { fa => // Triggering pure cancellation, then trying to get its result fa.cancel.flatMap(_ => fa) } task.runAsync // => CancellationException
Creates a new task that in case of error will fallback to the given backup task.
Creates a new task that will handle any matching throwable that this task might emit.
Creates a new task that will handle any matching throwable that this task might emit.
See onErrorRecover for the version that takes a partial function.
Creates a new task that will handle any matching throwable that this task might emit by executing another task.
Creates a new task that will handle any matching throwable that this task might emit by executing another task.
See onErrorRecoverWith for the version that takes a partial function.
Creates a new task that on error will try to map the error to another value using the provided partial function.
Creates a new task that on error will try to map the error to another value using the provided partial function.
See onErrorHandle for the version that takes a total function.
Creates a new task that will try recovering from an error by matching it with another task using the given partial function.
Creates a new task that will try recovering from an error by matching it with another task using the given partial function.
See onErrorHandleWith for the version that takes a total function.
Creates a new task that in case of error will retry executing the source again and again, until it succeeds.
Creates a new task that in case of error will retry executing the source again and again, until it succeeds.
In case of continuous failure the total number of executions
will be maxRetries + 1
.
Creates a new task that in case of error will retry executing the
source again and again, until it succeeds, or until the given
predicate returns false
.
Creates a new task that in case of error will retry executing the
source again and again, until it succeeds, or until the given
predicate returns false
.
In this sample we retry for as long as the exception is a TimeoutException
:
task.onErrorRestartIf { case _: TimeoutException => true case _ => false }
is the predicate that is executed if an error is thrown and
that keeps restarting the source for as long as it returns true
On error restarts the source with a customizable restart loop.
On error restarts the source with a customizable restart loop.
This operation keeps an internal state
, with a start value, an internal
state that gets evolved and based on which the next step gets decided,
e.g. should it restart, maybe with a delay, or should it give up and
re-throw the current error.
Example that implements a simple retry policy that retries for a maximum of 10 times before giving up; also introduce a 1 second delay before each retry is executed:
import scala.concurrent.duration._ task.onErrorRestartLoop(10) { (err, maxRetries, retry) => if (maxRetries > 0) // Next retry please; but do a 1 second delay retry(maxRetries - 1).delayExecution(1.second) else // No retries left, rethrow the error Task.raiseError(err) }
A more complex exponential back-off sample:
import scala.concurrent.duration._ // Keeps the current state, indicating the restart delay and the // maximum number of retries left final case class Backoff(maxRetries: Int, delay: FiniteDuration) // Restarts for a maximum of 10 times, with an initial delay of 1 second, // a delay that keeps being multiplied by 2 task.onErrorRestartLoop(Backoff(10, 1.second)) { (err, state, retry) => val Backoff(maxRetries, delay) = state if (maxRetries > 0) retry(Backoff(maxRetries - 1, delay * 2)).delayExecution(delay) else // No retries left, rethrow the error Task.raiseError(err) }
The given function injects the following parameters:
error
reference that was thrown
2. the current state
, based on which a decision for the retry is made
3. retry: S => Task[B]
function that schedules the next retry
is the initial state used to determine the next on error retry cycle
is a function that injects the current error, state, a function that can signal a retry is to be made and returns the next task
Returns a new value that transforms the result of the source,
given the recover
or map
functions, which get executed depending
on whether the result is successful or if it ends in error.
Returns a new value that transforms the result of the source,
given the recover
or map
functions, which get executed depending
on whether the result is successful or if it ends in error.
This is an optimization on usage of attempt and map, this equivalence being true:
task.redeem(recover, map) <-> task.attempt.map(_.fold(recover, map))
Usage of redeem
subsumes onErrorHandle
because:
task.redeem(fe, id) <-> task.onErrorHandle(fe)
is a function used for error recover in case the source ends in error
is a function used for mapping the result of the source in case it ends in success
Returns a new value that transforms the result of the source,
given the recover
or bind
functions, which get executed depending
on whether the result is successful or if it ends in error.
Returns a new value that transforms the result of the source,
given the recover
or bind
functions, which get executed depending
on whether the result is successful or if it ends in error.
This is an optimization on usage of attempt and flatMap, this equivalence being available:
task.redeemWith(recover, bind) <-> task.attempt.flatMap(_.fold(recover, bind))
Usage of redeemWith
subsumes onErrorHandleWith
because:
task.redeemWith(fe, F.pure) <-> task.onErrorHandleWith(fe)
Usage of redeemWith
also subsumes flatMap because:
task.redeemWith(Task.raiseError, fs) <-> task.flatMap(fs)
is the function that gets called to recover the source in case of error
is the function that gets to transform the source in case of success
Given a predicate function, keep retrying the task until the function returns true.
Triggers the asynchronous execution.
Triggers the asynchronous execution.
Without invoking runAsync
on a Task
, nothing
gets evaluated, as a Task
has lazy behavior.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is a callback that will be invoked upon completion
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a Cancelable that can be used to cancel a running task
Triggers the asynchronous execution.
Triggers the asynchronous execution.
Without invoking runAsync
on a Task
, nothing
gets evaluated, as a Task
has lazy behavior.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a CancelableFuture that can be used to extract the result or to cancel a running task.
Triggers the asynchronous execution, much like normal runAsync
,
but includes the ability to specify
Options that can modify the behavior
of the run-loop.
Triggers the asynchronous execution, much like normal runAsync
,
but includes the ability to specify
Options that can modify the behavior
of the run-loop.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is a callback that will be invoked upon completion
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a set of Options that determine the behavior of Task's run-loop.
a Cancelable that can be used to cancel a running task
Triggers the asynchronous execution, much like normal runAsync
,
but includes the ability to specify
Options that can modify the behavior
of the run-loop.
Triggers the asynchronous execution, much like normal runAsync
,
but includes the ability to specify
Options that can modify the behavior
of the run-loop.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a set of Options that determine the behavior of Task's run-loop.
a CancelableFuture that can be used to extract the result or to cancel a running task.
Similar to Scala's Future#onComplete
, this method triggers
the evaluation of a Task
and invokes the given callback whenever
the result is available.
Similar to Scala's Future#onComplete
, this method triggers
the evaluation of a Task
and invokes the given callback whenever
the result is available.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is a callback that will be invoked upon completion
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a Cancelable that can be used to cancel a running task
Tries to execute the source synchronously.
Tries to execute the source synchronously.
As an alternative to runAsync
, this method tries to execute
the source task immediately on the current thread and
call-stack.
WARNING: This method is a partial function, throwing exceptions in case errors happen immediately (synchronously).
Usage sample:
try task.runSyncMaybe match { case Right(a) => println("Success: " + a) case Left(future) => future.onComplete { case Success(a) => println("Async success: " + a) case Failure(e) => println("Async error: " + e) } } catch { case NonFatal(e) => println("Error: " + e) }
Obviously the purpose of this method is to be used for optimizations.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
Right(result)
in case a result was processed,
or Left(future)
in case an asynchronous boundary
was hit and further async execution is needed
runSyncStep for a variant that suspends evaluation in case an async boundary is hit
runSyncUnsafe, the blocking execution mode that can only work on top of the JVM.
A variant of runSyncMaybe that takes an implicit Task.Options from the current scope.
A variant of runSyncMaybe that takes an implicit Task.Options from the current scope.
This helps in tuning the evaluation model of task.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a set of Options that determine the behavior of Task's run-loop.
Right(result)
in case a result was processed,
or Left(future)
in case an asynchronous boundary
was hit and further async execution is needed
runSyncMaybe for the description.
Executes the source until completion, or until the first async boundary, whichever comes first.
Executes the source until completion, or until the first async boundary, whichever comes first.
This operation is mean to be compliant with
cats.effect.Effect.runSyncStep
, but without suspending the
evaluation in IO
.
Similar to runSyncMaybe,
except that in case of an async boundary, the evaluation is
suspended in Task
.
WARNING: This method is a partial function, throwing exceptions in case errors happen immediately (synchronously).
Usage sample:
try task.runSyncStep match { case Right(a) => println("Success: " + a) case Left(task) => task.runAsync.onComplete { case Success(a) => println("Async success: " + a) case Failure(e) => println("Async error: " + e) } } catch { case NonFatal(e) => println("Error: " + e) }
Obviously the purpose of this method is to be used for optimizations.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
Right(result)
in case a result was processed,
or Left(task)
in case an asynchronous boundary
was hit and further async execution is needed
runSyncMaybe for the variant that returns a CancelableFuture
runSyncUnsafe, the blocking execution mode that can only work on top of the JVM.
A variant of runSyncStep that takes an implicit Task.Options from the current scope.
A variant of runSyncStep that takes an implicit Task.Options from the current scope.
This helps in tuning the evaluation model of task.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a set of Options that determine the behavior of Task's run-loop.
Right(result)
in case a result was processed,
or Left(task)
in case an asynchronous boundary
was hit and further async execution is needed
Evaluates the source task synchronously and returns the result immediately or blocks the underlying thread until the result is ready.
Evaluates the source task synchronously and returns the result immediately or blocks the underlying thread until the result is ready.
WARNING: blocking operations are unsafe and incredibly
error prone on top of the JVM. It's a good practice to not block
any threads and use the asynchronous runAsync
methods instead.
In general prefer to use the asynchronous
.runAsync and to
structure your logic around asynchronous actions in a
non-blocking way. But in case you're blocking only once, in
main
, at the "edge of the world" so to speak, then it's OK.
Sample:
import scala.concurrent.duration._ task.runSyncUnsafe(3.seconds)
This is equivalent with:
import scala.concurrent.Await Await.result(task.runAsync, 3.seconds)
Some implementation details:
BlockingContext
(scala.concurrent.blocking
), just like
Scala's Await.result
timeout
is mandatory, just like when using Scala's
Await.result
, in order to make the caller aware that the
operation is dangerous and that setting a timeout
is good
practiceNot supported on top of JavaScript engines and trying to use it with Scala.js will trigger a compile time error.
For optimizations on top of JavaScript you can use runSyncMaybe or runSyncStep instead.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is a duration that specifies the
maximum amount of time that this operation is allowed to block the
underlying thread. If the timeout expires before the result is ready,
a TimeoutException
gets thrown. Note that you're allowed to
pass an infinite duration (with Duration.Inf
), but unless
it's main
that you're blocking and unless you're doing it only
once, then this is definitely not recommended — provide a finite
timeout in order to avoid deadlocks.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
is an implicit value that's only available for the JVM and not for JavaScript, its purpose being to stop usage of this operation on top of engines that do not support blocking threads.
Variant of runSyncUnsafe that takes a Task.Options implicitly from the scope in order to tune the evaluation model of the task.
Variant of runSyncUnsafe that takes a Task.Options implicitly from the scope in order to tune the evaluation model of the task.
UNSAFE — this operation can trigger the execution of side effects, which break referential transparency and is thus not a pure function.
In FP code use with care, suspended in another Task
or Coeval, or at the edge of the
FP program.
is a duration that specifies the
maximum amount of time that this operation is allowed to block the
underlying thread. If the timeout expires before the result is ready,
a TimeoutException
gets thrown. Note that you're allowed to
pass an infinite duration (with Duration.Inf
), but unless
it's main
that you're blocking and unless you're doing it only
once, then this is definitely not recommended — provide a finite
timeout in order to avoid deadlocks.
is an injected
Scheduler that gets used
whenever asynchronous boundaries are needed when
evaluating the task; a Scheduler
is in general needed
when the Task
needs to be evaluated via runAsync
a set of Options that determine the behavior of Task's run-loop.
is an implicit value that's only available for the JVM and not for JavaScript, its purpose being to stop usage of this operation on top of engines that do not support blocking threads.
Start execution of the source suspended in the Task
context.
Start execution of the source suspended in the Task
context.
This can be used for non-deterministic / concurrent execution. The following code is more or less equivalent with Task.parMap2 (minus the behavior on error handling and cancellation):
def par2[A, B](ta: Task[A], tb: Task[B]): Task[(A, B)] = for { fa <- ta.start fb <- tb.start a <- fa b <- fb } yield (a, b)
Note in such a case usage of parMap2 (and parMap3, etc.) is still recommended because of behavior on error and cancellation — consider that in the example above, if the first task finishes in error, the second task doesn't get cancelled.
This operation forces an asynchronous boundary before execution
Returns a Task that mirrors the source Task but that triggers a
TimeoutException
in case the given duration passes without the
task emitting any item.
Returns a Task that mirrors the source Task but switches to the given backup Task in case the given duration passes without the source emitting any item.
Generic conversion of Task
to any data type for which there's
a TaskLift implementation available.
Generic conversion of Task
to any data type for which there's
a TaskLift implementation available.
Supported data types:
monix.reactive.Observable
monix.tail.Iterant
This conversion guarantees:
Sample:
Task(1 + 1) .delayExecution(5.seconds) .to[IO]
Converts the source Task
to any data type that implements
Async.
Converts the source Task
to any data type that implements
Async.
Example:
import cats.effect.IO Task.eval(println("Hello!")) .delayExecution(5.seconds) .toAsync[IO]
An Effect[Task]
instance is needed in scope,
which might need a Scheduler to
be available. Such a requirement is needed because the Task
has to be evaluated in order to be converted.
NOTE: the resulting instance will NOT be cancelable, as in
Task's cancelation token doesn't get carried over. This is
implicit in the usage of cats.effect.Async
type class.
In the example above what this means is that the task will
still print "Hello!"
after 5 seconds, even if the resulting
task gets cancelled.
is the cats.effect.Async
instance required in
order to perform the conversion
is the Effect[Task]
instance needed to
evaluate tasks; when evaluating tasks, this is the pure
alternative to demanding a Scheduler
toConcurrent that is able to convert to cancelable values via the Concurrent type class.
to that is able to convert to any data type that has a TaskLift implementation
Converts the source Task
to any data type that implements
Concurrent.
Converts the source Task
to any data type that implements
Concurrent.
Example:
import cats.effect.IO Task.eval(println("Hello!")) .delayExecution(5.seconds) .to[IO]
A ConcurrentEffect
instance for Task
is also needed in scope,
which might need a Scheduler to
be available. Such a requirement is needed because the Task
has to be evaluated in order to be converted.
NOTE: the resulting value is cancelable, via usage of
cats.effect.Concurrent
.
is the cats.effect.Concurrent
instance required in
order to perform the conversion
is the ConcurrentEffect[Task]
instance needed to
evaluate tasks; when evaluating tasks, this is the pure
alternative to demanding a Scheduler
Converts the source to a cats.effect.IO
value.
Converts the source to a cats.effect.IO
value.
val task: Task[Unit] = Task .eval(println("Hello!")) .delayExecution(5.seconds) // Conversion; note the resulting IO is also // cancelable if the source is val io: IO[Unit] = task.toIO
This is an alias for toConcurrent, but specialized for IO
.
You can use either with the same result.
is the ConcurrentEffect[Task]
instance needed to
evaluate tasks; when evaluating tasks, this is the pure
alternative to demanding a Scheduler
Converts a Task to an org.reactivestreams.Publisher
that
emits a single item on success, or just the error on failure.
Converts a Task to an org.reactivestreams.Publisher
that
emits a single item on success, or just the error on failure.
See reactive-streams.org for the Reactive Streams specification.
Returns a string representation of this task meant for debugging purposes only.
Returns a string representation of this task meant for debugging purposes only.
Makes the source Task
uninterruptible such that a cancel
signal
(e.g.
Makes the source Task
uninterruptible such that a cancel
signal
(e.g. Fiber.cancel) has no effect.
val uncancelable = Task .eval(println("Hello!")) .delayExecution(10.seconds) .uncancelable .runAsync // No longer works uncancelable.cancel() // After 10 seconds //=> Hello!
DEPRECATED - renamed to autoCancelable, in order to differentiate
it from the Task.cancelable
builder.
DEPRECATED - renamed to autoCancelable, in order to differentiate
it from the Task.cancelable
builder.
(Since version 3.0.0) Renamed to autoCancelable
DEPRECATED - replace with usage of Task.runSyncMaybe:
DEPRECATED - replace with usage of Task.runSyncMaybe:
task.coeval <-> Coeval(task.runSyncMaybe)
(Since version 3.0.0-RC2) Replaced with start
DEPRECATED - please use flatMap.
DEPRECATED - please use flatMap.
The reason for the deprecation is that this operation is
redundant, as it can be expressed with flatMap
, with the
same effect:
trigger.flatMap(_ => task)
The syntax provided by Cats can also help:
import cats.syntax.all._
trigger *> task
(Since version 3.0.0) Please use flatMap
DEPRECATED - please use flatMap.
DEPRECATED - please use flatMap.
The reason for the deprecation is that this operation is
redundant, as it can be expressed with flatMap
and map
,
with the same effect:
task.flatMap(a => selector(a).map(_ => a))
(Since version 3.0.0) Please rewrite in terms of flatMap
DEPRECATED - renamed to executeAsync.
DEPRECATED - renamed to executeAsync.
The reason for the deprecation is the repurposing of the word "fork".
(Since version 3.0.0) Renamed to Task!.executeAsync
DEPRECATED - subsumed by start.
DEPRECATED - subsumed by start.
To be consistent with cats-effect 1.0.0, start
now
enforces an asynchronous boundary, being exactly the same
as fork
from 3.0.0-RC1
(Since version 3.0.0-RC2) Replaced with start
Deprecated — use redeem instead.
Deprecated — use redeem instead.
Task.redeem is the same operation, but with a different name and the
function parameters in an inverted order, to make it consistent with fold
on Either
and others (i.e. the function for error recovery is at the left).
(Since version 3.0.0-RC2) Please use Task.redeem
Deprecated — use redeemWith instead.
Deprecated — use redeemWith instead.
Task.redeemWith is the same operation, but with a different name and the
function parameters in an inverted order, to make it consistent with fold
on Either
and others (i.e. the function for error recovery is at the left).
(Since version 3.0.0-RC2) Please use Task.redeemWith
Deprecated — switch to Task.parZip2, which has the same behavior.
Deprecated — switch to Task.parZip2, which has the same behavior.
(Since version 3.0.0-RC2) Switch to Task.parZip2
Deprecated — switch to Task.parMap2, which has the same behavior.
Deprecated — switch to Task.parMap2, which has the same behavior.
(Since version 3.0.0-RC2) Use Task.parMap2
Task
represents a specification for a possibly lazy or asynchronous computation, which when executed will produce anA
as a result, along with possible side-effects.Compared with
Future
from Scala's standard library,Task
does not represent a running computation or a value detached from time, asTask
does not execute anything when working with its builders or operators and it does not submit any work into any thread-pool, the execution eventually taking place only afterrunAsync
is called and not before that.Note that
Task
is conservative in how it spawns logical threads. Transformations likemap
andflatMap
for example will default to being executed on the logical thread on which the asynchronous computation was started. But one shouldn't make assumptions about how things will end up executed, as ultimately it is the implementation's job to decide on the best execution model. All you are guaranteed is asynchronous execution after executingrunAsync
.Getting Started
To build a
Task
from a by-name parameters (thunks), we can use Task.eval or Task.apply:Nothing gets executed yet, as
Task
is lazy, nothing executes until you trigger .runAsync on it.To combine
Task
values we can use .map and .flatMap, which describe sequencing and this time it's in a very real sense because of the laziness involved:This
Task
reference will trigger a side effect on evaluation, but not yet. To make the above print its message:The returned type is a CancelableFuture which inherits from Scala's standard Future, a value that can be completed already or might be completed at some point in the future, once the running asynchronous process finishes. Such a future value can also be canceled, see below.
Laziness
The fact that
Task
is lazy whereasFuture
is not has real consequences. For example withTask
you can do this:Future
being a strict value-wannabe means that the actual value gets "memoized" (means cached), howeverTask
is basically a function that can be repeated for as many times as you want.Task
can also do memoization of course:The difference between this and just calling
runAsync()
is thatmemoize()
still returns aTask
and the actual memoization happens on the firstrunAsync()
(with idempotency guarantees of course).But here's something else that the
Future
data type cannot do:This keeps repeating the computation for as long as the result is a failure and caches it only on success. Yes we can!
Parallelism
Because of laziness, invoking Task.sequence will not work like it does for
Future.sequence
, the givenTask
values being evaluated one after another, in sequence, not in parallel. If you want parallelism, then you need to use Task.gather and thus be explicit about it.This is great because it gives you the possibility of fine tuning the execution. For example, say you want to execute things in parallel, but with a maximum limit of 30 tasks being executed in parallel. One way of doing that is to process your list in batches:
Note that the built
Task
reference is just a specification at this point, or you can view it as a function, as nothing has executed yet, you need to call .runAsync explicitly.Cancellation
The logic described by an
Task
task could be cancelable, depending on how theTask
gets built.CancelableFuture references can also be canceled, in case the described computation can be canceled. When describing
Task
tasks withTask.eval
nothing can be cancelled, since there's nothing about a plain function that you can cancel, but we can build cancelable tasks with Task.cancelable.The sample above prints a message with a delay, where the delay itself is scheduled with the injected
Scheduler
. TheScheduler
is in fact an implicit parameter torunAsync()
.This action can be cancelled, because it specifies cancellation logic. In case we have no cancelable logic to express, then it's OK if we returned a Cancelable.empty reference, in which case the resulting
Task
would not be cancelable.But the
Task
we just described is cancelable, for one at the edge, due torunAsync
returning Cancelable and CancelableFuture references:But also cancellation is described on
Task
as a pure action, which can be used for example in race conditions:The returned type in
racePair
is Fiber, which is a data type that's meant to wrap tasks linked to an active process and that can be canceled or joined.Also, given a task, we can specify actions that need to be triggered in case of cancellation, see doOnCancel:
Controlling cancellation can be achieved with cancelable and uncancelable.
The former activates auto-cancelable flatMap chains, whereas the later ensures that a task becomes uncancelable such that it gets executed as an atomic unit (either all or nothing).
Note on the ExecutionModel
Task
is conservative in how it introduces async boundaries. Transformations likemap
andflatMap
for example will default to being executed on the current call stack on which the asynchronous computation was started. But one shouldn't make assumptions about how things will end up executed, as ultimately it is the implementation's job to decide on the best execution model. All you are guaranteed (and can assume) is asynchronous execution after executingrunAsync
.Currently the default ExecutionModel specifies batched execution by default and
Task
in its evaluation respects the injectedExecutionModel
. If you want a different behavior, you need to execute theTask
reference with a different scheduler.