class Task[out T <: Shareable]
A lightweight thread, running a block concurrently with the others, and
the handle to its result T.
Tasks switch cooperatively, as in Kotlin: at operations that wait (a
channel operation that must wait, Task.sleep, #await, IO) and at
Task.yield; a loop that only computes keeps its worker until it calls
Task.yield. A wait suspends only the calling task;
the others go on running. Values cross between tasks only when they are
Shareable.
t = Task.spawn { [1, 2, 3].sum }
t.await # => 6
Task.scope { |s| [s.spawn { 1 }, s.spawn { 2 }].map(&:await) } # => [1, 2]
<prelude>:5716
Classes and modules
Task::CancelledRaised in a cancelled task (Task#cancel, Scope#cancel, a failing Task.scope). A direct subclass of Exception: rescue => e does not catch it, and ensure runs as usual.Task::TimeoutRaised by Task.timeout when the block has not finished in time.
Class methods
def self.spawn[R](&: () -> R) -> Task[R]
Starts a task running the block and returns it at once.
The block captures by value: every value it captures must be Shareable,
and it cannot assign to outer variables. Without a Task.scope, the
task belongs to the program's root scope and is dropped when the main
code finishes; #await it, or use Task.scope, to wait for it.
<prelude>:5725
def self.scope[R](&: (Scope) -> R) -> R
Runs the block with a Scope, waits for every task started with
Scope#spawn to finish, and returns the block's value.
When a task of the scope or the block itself raises, the remaining tasks are cancelled and waited for, and the first exception is raised again here (the others are dropped). When the task running the scope is cancelled, the children are cancelled and waited for too.
Task.scope do |s|
s.spawn { raise ArgumentError, "bad" }
s.spawn { Task.sleep(1.second) } # cancelled
nil
end
# raises ArgumentError
<prelude>:5742
def self.sleep(duration: Duration) -> Void
Suspends the calling task for duration; the other tasks run
meanwhile.
Raises
Task::Cancelled when the task is cancelled while it sleeps.
<prelude>:5749
def self.yield -> Void
Suspends the calling task briefly, so that the scheduler can run other
runnable tasks, then raises a cancellation (Task::Cancelled) or an
exit from another task that came meanwhile, as a wait does.
With one worker thread (QUARZ_THREADS=1), the tasks that were
runnable run first, each until it finishes, waits or yields, and the
caller goes on after them. With several, the others run in parallel
and need not have run, nor finished, when the caller goes on: wait for
a task (#await) or use a Channel to know that it did something.
<prelude>:5759
def self.current -> Task[Object]
The task running the caller (the main code is a task too).
<prelude>:5761
def self.timeout[R](duration: Duration, &: () -> R) -> R
Runs the block in a child task and returns its value, or raises
Task::Timeout when it has not finished within duration.
The block is cancelled then, and waited for before the call raises: it
is interrupted only at a wait (as by #cancel), so a computation that
never waits runs to its end first. Cancelling the calling task cancels
the block too.
Task.timeout(1.second) { 42 } # => 42
Task.timeout(10.ms) { Task.sleep(1.second) } # raises Task::Timeout
<prelude>:5774
def self.blocking[R](&: () -> R) -> R
Runs the block on a helper thread and returns its value (or raises its exception); only the calling task waits.
For long computations and blocking foreign calls: the standard library's IO already suspends only the task.
The block is copied to the helper thread, so it may capture only
immutable values (numbers, strings, collections, data values) and
return only those; what it prints is written out when it finishes.
Cancelling the waiting task raises in it, but the block runs to its
end. Tasks the block spawned and left running are cancelled when it
returns, and waited for (their ensure clauses run), as a scope's.
Raises
ArgumentError when the block captures or returns a value that cannot
be copied: a Channel, an Atom, a Task, a mutable object.
<prelude>:5792
def self.check_cancel -> Void
Raises Task::Cancelled when the calling task has been cancelled
(outside .uncancellable); otherwise does nothing.
It does not let other tasks run: a loop that only computes calls
.yield instead, which checks too, or a task of its own worker could
not run to cancel it.
<prelude>:5799
def self.uncancellable[R](&: () -> R) -> R
Runs the block out of reach of the calling task's cancellation: its
waits do not raise Task::Cancelled, as in Trio's shielded cancel
scope and Kotlin's NonCancellable context. For cleanup that must wait
(an ensure that tells a peer goodbye): a cancelled task raises
Task::Cancelled at every wait, and would be interrupted again. Once
the block ends, the cancellation applies again. Give the wait its own
limit (Task.timeout), or a stuck peer holds the task forever. An
exit from another task still reaches it (Kernel#exit).
started = Channel[Nil].new(capacity: 1)
cleaned = Channel[Bool].new(capacity: 1)
t = Task.spawn do
begin
started.send(nil)
Task.sleep(1.second)
ensure
Task.uncancellable do
Task.sleep(1.ms) # waits, though cancelled
cleaned.send(true)
end
end
end
started.receive
t.cancel
r = begin
t.await
:finished
rescue Task::Cancelled
:cancelled
end
r # => :cancelled
cleaned.receive # => true
<prelude>:5834
Methods
def await -> T
Waits for the task to finish and returns its value (at once when it has finished); only the calling task waits.
Awaiting again returns the same value.
Raises
The exception the task ended with, Task::Cancelled when it was
cancelled.
<prelude>:5855
def result[U >: T] -> Result[U, StandardError]
The task's value as a Result::Ok, or the StandardError it ended
with as a Result::Err: Result.capture { await }. Waits as #await
does. U is T unless written: t.result[Int?]() for a wider one
(Task is covariant in T, Result is not).
Raises
Task::Cancelled when it was cancelled, and the exceptions that are
not StandardErrors it ended with.
<prelude>:5865
def cancel -> Void
Asks the task to stop: it raises Task::Cancelled at its next wait (IO,
Task.sleep, #await), channel send, receive or select (even
one that need not wait), Task.yield or Task.check_cancel,
so its ensure clauses run. The task stays cancelled: every wait after
raises it again (a task that rescues it cannot go on waiting), but
inside Task.uncancellable.
A task cancelled before it starts does not run its block. Does nothing when the task has finished; cancelling twice is cancelling once.
<prelude>:5875
def done? -> Bool
Whether the task has finished, by returning, raising or being cancelled.
<prelude>:5878
def cancelled? -> Bool
Whether the task has been cancelled (#cancel, or by its scope or
Task.timeout): it stays so.
<prelude>:5881