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

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