Menu
Reference
- Manticore\Ds
- Async
- Classes
- AggregateError
- CancellationToken
- CancelledException
- Channel
- ChannelClosedException
- Context
- DeadlockException
- Mutex
- Once
- Pool
- Received
- SelectCase
- Selected
- Semaphore
- Settled
- Task
- TaskGroup
- TimeoutException
- Functions
- async()
- awaitAll()
- awaitAllSettled()
- awaitAny()
- channel()
- delay()
- dump()
- dumpOn()
- failure()
- group()
- mapConcurrent()
- mapSettled()
- readFile()
- select()
- selectNow()
- selectWithin()
- shield()
- shutdownOn()
- spawn()
- stats()
- timeout()
- watchdog()
- writeFile()
- Http
- Http\WebSocket
- Buffer
- Process
- Io\Poll
- Manticore\Sapi
- Ffi
TaskGroup
final class TaskGroup
A structured-concurrency scope (a "nursery").
Every spawn() attaches to the innermost scope open in the calling task. The scope does not close until all of its children have settled, so a task can never outlive the code that opened the scope. The first child failure cancels the siblings and propagates out of the scope once they have been joined. Cancellation is recursive: cancelling a scope cancels the scopes nested in it.
Open a scope with group() or TaskGroup::run(). The scope lives on the task, not in a global stack, so scopes opened by concurrently running tasks never adopt each other's children. async() opens an implicit root scope, which is why even a top-level spawn() is joined.
Async\group(function (TaskGroup $g) {
$g->spawn(fn() => work(1));
$g->spawn(fn() => work(2));
}); // returns only once both children are doneExample
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
use function Async\group;
async(function () {
// group() opens a child scope. It returns the body's value, but only
// after every task spawned inside has settled.
$answer = group(function (TaskGroup $g) {
$g->spawn(function () {
delay(0.02);
echo "second child done\n";
});
$g->spawn(function () {
delay(0.01);
echo "first child done\n";
});
echo "body finished\n";
return 42;
});
echo "group returned $answer\n";
});Output
body finished
first child done
second child done
group returned 42Example
use Async\CancelledException;
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
try {
async(function () {
TaskGroup::run(function (TaskGroup $g) {
$g->spawn(function () {
try {
delay(10.0);
} catch (CancelledException $e) {
echo "sibling cancelled\n";
throw $e;
}
});
$g->spawn(function () {
delay(0.01);
throw new RuntimeException("child failed");
});
});
echo "not reached\n";
});
} catch (RuntimeException $e) {
// The first failure cancelled the sibling, the scope joined it, and the
// original exception came out of the scope.
echo "caught: ", $e->getMessage(), "\n";
}Output
sibling cancelled
caught: child failedSee also: group, Task, CancellationToken
Methods
token() | The read-only cancellation handle for this scope. |
run() | Open a scope, run $body($group), then wait for every child before returning. |
spawn() | Spawn a child task into this scope and return a handle to await. |
cancel() | Cancel every live child of this scope. |
token()#source
function token(): CancellationToken
The read-only cancellation handle for this scope.
Pass the token to code that should observe cancellation without being able to cause it.
Returns
CancellationToken — a view of this scope
See also: CancellationToken
run()#source
static function run(callable $body): mixed
Open a scope, run $body($group), then wait for every child before returning.
Returns the body's value. If the body throws or a child fails, the remaining children are cancelled and joined first, and then the first failure is thrown; a CancelledException that cancelled the scope itself is never swallowed. group() is the everyday spelling of this.
Parameters
$bodycallable- receives the new
TaskGroup; its return value is returned
Returns
mixed — the body's return value
Throws
- \Throwable
- the first failure in the scope
Example
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
use function Async\group;
async(function () {
// group() opens a child scope. It returns the body's value, but only
// after every task spawned inside has settled.
$answer = group(function (TaskGroup $g) {
$g->spawn(function () {
delay(0.02);
echo "second child done\n";
});
$g->spawn(function () {
delay(0.01);
echo "first child done\n";
});
echo "body finished\n";
return 42;
});
echo "group returned $answer\n";
});Output
body finished
first child done
second child done
group returned 42Example
use Async\CancelledException;
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
try {
async(function () {
TaskGroup::run(function (TaskGroup $g) {
$g->spawn(function () {
try {
delay(10.0);
} catch (CancelledException $e) {
echo "sibling cancelled\n";
throw $e;
}
});
$g->spawn(function () {
delay(0.01);
throw new RuntimeException("child failed");
});
});
echo "not reached\n";
});
} catch (RuntimeException $e) {
// The first failure cancelled the sibling, the scope joined it, and the
// original exception came out of the scope.
echo "caught: ", $e->getMessage(), "\n";
}Output
sibling cancelled
caught: child failedSee also: group
spawn()#source
function spawn(callable $fn, mixed ...$args): Task
Spawn a child task into this scope and return a handle to await.
The task is owned by this scope and joined when the scope closes. The task starts when the calling task next suspends. Spawning into a scope that is already cancelled starts the child, which is cancelled at its first suspend point.
Parameters
$fncallable- the task body
...$argsmixed- passed to
$fn
Returns
Task — the handle; await() it or let the scope join it
Example
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
use function Async\group;
async(function () {
// group() opens a child scope. It returns the body's value, but only
// after every task spawned inside has settled.
$answer = group(function (TaskGroup $g) {
$g->spawn(function () {
delay(0.02);
echo "second child done\n";
});
$g->spawn(function () {
delay(0.01);
echo "first child done\n";
});
echo "body finished\n";
return 42;
});
echo "group returned $answer\n";
});Output
body finished
first child done
second child done
group returned 42See also: spawn
cancel()#source
function cancel(): void
Cancel every live child of this scope. The write half of cancellation.
Handlers registered with CancellationToken::onCancel() run first, synchronously. Then each parked child is taken off whatever it waits on and gets a CancelledException at its suspend point; a child that opened scopes of its own unwinds through their joins, so cancellation is recursive. A child that calls cancel() on its own scope is cancelled too. Cancelling twice does nothing, and the call does not wait: the scope still joins its children when it closes. Cancelling a scope is not a failure of the scope.
Example
use Async\CancelledException;
use Async\TaskGroup;
use function Async\async;
use function Async\delay;
use function Async\group;
async(function () {
group(function (TaskGroup $g) {
$token = $g->token();
$token->onCancel(fn() => print("onCancel hook ran\n"));
// A task that suspends is interrupted with CancelledException,
// which extends \Error: catch (\Exception) cannot swallow it.
$g->spawn(function () {
try {
delay(10.0);
} catch (CancelledException $e) {
echo "sleeper cancelled\n";
throw $e;
}
});
// A loop that never suspends polls the token instead.
$g->spawn(function () use ($g, $token) {
for ($step = 1; $step <= 5; $step++) {
if ($step === 3) {
$g->cancel(); // the write half; $token is the read-only view
}
if ($token->isCancelled()) {
echo "loop stopped at step $step\n";
return;
}
echo "step $step\n";
}
});
});
echo "scope joined\n";
});Output
step 1
step 2
onCancel hook ran
loop stopped at step 3
sleeper cancelled
scope joinedSee also: CancellationToken, Task::cancel