Standard Library
class TaskGroup
A set of tasks that live and end together.
since 0.1.0-alpha.1linuxwindows
- bases
IDisposable
Overview
A task group is structured concurrency without new syntax: it is a resource, so using TaskGroup g = TaskGroup(); calls close on every way out of the block (reaching the end, return, throw or break). close cancels the children that are still waiting and then waits for all of them, so no task outlives the block that started it.
run starts a child on the same thread as the caller: it is cheap and can be cancelled. That is different from spawn, which starts a separate thread that runs in parallel and cannot be cancelled. A child only notices a cancellation when it next waits (at an await); it is never interrupted in the middle of running. A CancelledException that a child does not catch is absorbed at the group boundary, because cancellation is not an error; any other uncaught exception stays an error.
Description
A TaskGroup owns a set of tasks. It implements IDisposable, so the way to use it is a using
declaration:
using TaskGroup g = TaskGroup();
The group's close() runs on every way out of the block (reaching the end, return, a throw,
break). close() cancels any task that is still alive and then waits for every task in the group
to finish. By the time execution leaves the block, no task started in it is still running.
run(body)startsbody, a closure with no parameters, as a new task owned by this group. It returns immediately; the task runs when the current one waits.cancelAll()marks every task of the group cancelled. Each one receives aCancelledExceptionat its nextawaitthat waits (seelang.cancellation).close()iscancelAll()followed by waiting for all tasks. It is whatusingcalls for you.
A group task is a same-thread task: cheap, and cancellable. It is deliberately not the same as
std::spawn, which starts a worker on another thread that runs in parallel and cannot be
cancelled. The two do not blur.
A group cancels its stragglers when the block ends
Promise<int> after(int ms, int value) {
Promise<int> p = Promise();
std::sysTimerStart(ms, 0, (n) => p.resolve(value));
return p;
}
void run() {
using TaskGroup g = TaskGroup();
g.run(() => {
console.writeln("child 1 start");
int v = await after(10, 1);
console.writeln("child 1 done");
});
g.run(() => {
console.writeln("child 2 start");
int v = await after(10000, 2);
console.writeln("child 2 never finishes");
});
console.writeln("group body done");
int w = await after(50, 0);
console.writeln("leaving the block");
}
run();
console.writeln("after the block");
group body done
child 1 start
child 2 start
child 1 done
leaving the block
after the block
Child 1 finishes on its own. Child 2 is still waiting when the block ends, so it is cancelled (it never prints) instead of running for ten seconds.
Rules
- Always declare the group with
using. Without it nothing closes the group, and its tasks can outlive the scope you meant to bound them with. - Tasks started with
rundo not begin until the current task waits; the body that calledrunkeeps running first, as the output above shows. - An uncaught
CancelledExceptionin a child is absorbed by the group. Any other uncaught exception in a child is an uncaught program error, as everywhere else. - A child may catch
CancelledExceptionand refuse to stop.close()then keeps waiting until it eventually waits or returns. - While a task is running
close()for its own group, new cancellation is held back until the close finishes, so a cancelled task that uses a group cannot deadlock. TaskGroupis for same-thread tasks only. AWorkeris never part of a group and is never cancelled by one.
Examples
A child may catch the cancellation to clean up, and then rethrow it so the group absorbs it. The
message is task cancelled:
Cleaning up when cancelled
Promise<int> after(int ms, int value) {
Promise<int> p = Promise();
std::sysTimerStart(ms, 0, (n) => p.resolve(value));
return p;
}
void run() {
using TaskGroup g = TaskGroup();
g.run(() => {
try {
console.writeln("slow child waiting");
int v = await after(10000, 2);
console.writeln("slow child finished");
} catch (ICancelledException e) {
console.writeln("slow child cancelled: ${e.message}");
throw e;
}
});
int w = await after(20, 0);
console.writeln("leaving the block");
}
run();
console.writeln("after the block");
slow child waiting
leaving the block
slow child cancelled: task cancelled
after the block
Notes
TaskGroup is one of the library types that implement IDisposable, with File and
InStream<T>.
Examples
A group cancels its stragglers when the block ends
Promise<int> never = Promise();
void work() {
using TaskGroup g = TaskGroup();
g.run(() => { console.writeln("quick child"); });
g.run(() => {
try { int x = await never; }
catch (ICancelledException e) { console.writeln("slow child cancelled"); throw e; }
});
console.writeln("leaving the block");
}
work();
console.writeln("after the group");
leaving the block
quick child
slow child cancelled
after the group
Constructors
new
new()Create an empty task group.
Examples
TaskGroup g = TaskGroup();
console.writeln("empty group created");
g.close();
console.writeln("closed with no children");
empty group created
closed with no children
Methods
cancelAll
cancelAll() -> voidAsk every child of the group to stop.
Each child that is still running is marked cancelled, and the cancellation is delivered to it as a CancelledException the next time it waits. Nothing is interrupted in the middle of running, and the call returns at once without waiting for the children.
Examples
Promise<int> never = Promise();
TaskGroup g = TaskGroup();
g.run(() => {
try { int x = await never; }
catch (ICancelledException e) { console.writeln("child saw cancellation"); throw e; }
});
g.cancelAll();
console.writeln("cancel requested");
g.close();
console.writeln("closed");
cancel requested
child saw cancellation
closed
close
close() -> voidCancel the remaining children and wait for all of them to finish.
close is what using calls when the block ends. It marks every child cancelled, then waits until each one is done, so when it returns no child is left running. A cancelled child that does not catch the CancelledException ends quietly. Closing the group of a task that is itself being cancelled still completes the wait; that cancellation is delivered afterwards, at the task's next ordinary wait.
Examples
Promise<int> never = Promise();
TaskGroup g = TaskGroup();
g.run(() => { int x = await never; console.writeln("unreachable"); });
g.close();
console.writeln("closed without error");
closed without error
run
run(() => void body) -> voidStart a child task owned by this group.
The child runs on the same thread as the caller, starting the next time the caller waits or when the group is closed. It is cancelled by cancelAll or close. An uncaught CancelledException in the child is absorbed; any other uncaught exception is reported as usual.
Parameters
- body
- The function the child runs.
Examples
Children run when the group is closed
TaskGroup g = TaskGroup();
g.run(() => { console.writeln("child one"); });
g.run(() => { console.writeln("child two"); });
console.writeln("children registered");
g.close();
console.writeln("all children finished");
children registered
child one
child two
all children finished
See also
- awaitTimeout — Wait for a promise, but give up after a time limit.
- Worker — The handle to a value that another worker is computing.
- CancelledException — The error delivered to a task that has been cancelled.
- IDisposable — The interface of an object that must be cleaned up when its work is finished.
- Promise — A value that arrives later.