Skip to content
This repository was archived by the owner on Jun 16, 2025. It is now read-only.

Commit eec7c7e

Browse files
committed
Require Scope parameter in TaskGroup constructor; update documentation for TaskGroup and Scope usage
1 parent 51c8f90 commit eec7c7e

File tree

2 files changed

+59
-13
lines changed

2 files changed

+59
-13
lines changed

basic.md

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,14 @@ Goodbye, World
109109
```php
110110
function mergeFiles(string ...$files): string
111111
{
112-
$taskGroup = new Async\TaskGroup(captureResults: true);
112+
$scope = new Async\Scope();
113+
$taskGroup = new Async\TaskGroup(scope: $scope, captureResults: true);
113114

114115
foreach ($files as $file) {
115116
$taskGroup->spawn(file_get_contents(...), $file);
116117
}
117118

118-
return array_merge("\n", await $taskGroup->all());
119+
return array_merge("\n", await $taskGroup);
119120
}
120121
```
121122

@@ -160,7 +161,7 @@ function fetchUserProfile(string $userId): array
160161
{
161162
async inherit $userDataScope {
162163

163-
$taskGroup = new Async\TaskGroup($userDataScope);
164+
$taskGroup = new Async\TaskGroup($userDataScope, captureResults: true);
164165

165166
$taskGroup->add(spawn fetchUserData());
166167
$taskGroup->add(spawn fetchUserSettings($userId));
@@ -1658,7 +1659,7 @@ function generateReport(): void
16581659
try {
16591660
async inherit $scope {
16601661

1661-
$taskGroup = new TaskGroup();
1662+
$taskGroup = new TaskGroup($scope);
16621663

16631664
[$employees, $salaries, $workHours] = await $taskGroup->add([
16641665
spawn fetchEmployees(),
@@ -1982,7 +1983,8 @@ can set a custom timeout for that specific `Scope` using the `Scope::disposeAfte
19821983
```php
19831984
function mergeFiles(string ...$files): string
19841985
{
1985-
$taskGroup = new Async\TaskGroup(captureResults: true);
1986+
$scope = new Async\Scope();
1987+
$taskGroup = new Async\TaskGroup($scope, captureResults: true);
19861988

19871989
foreach ($files as $file) {
19881990
$taskGroup->add(spawn file_get_contents($file));
@@ -2026,7 +2028,8 @@ If the results are no longer needed, the `TaskGroup::disposeResults()` method sh
20262028
function processInBatches(array $files, int $limit): array
20272029
{
20282030
$allResults = [];
2029-
$taskGroup = new Async\TaskGroup(captureResults: true);
2031+
$scope = new Async\Scope();
2032+
$taskGroup = new Async\TaskGroup($scope, captureResults: true);
20302033
$count = 0;
20312034

20322035
foreach ($files as $file) {
@@ -2063,7 +2066,44 @@ The reason for this behavior lies in the fact that `TaskGroup` only keeps track
20632066
If a task group is being disposed, it means the user clearly understands
20642067
that all coroutines launched within it should also be terminated.
20652068

2066-
#### Combining TaskGroup and Scope
2069+
#### Adding coroutines to a `TaskGroup`
2070+
2071+
The method `TaskGroup::add` adds a new coroutine to the `TaskGroup`. It performs the following checks:
2072+
* the coroutine must not be completed
2073+
* the coroutine must belong to the same `Scope` associated with the `TaskGroup`
2074+
* A task must not be added more than once.
2075+
2076+
If these checks fail, an exception is thrown.
2077+
2078+
```php
2079+
use Async\TaskGroup;
2080+
2081+
$scope = new Async\Scope();
2082+
$taskGroup = new Async\TaskGroup($scope);
2083+
2084+
$taskGroup->add(spawn {return;}); // <- Exception: Wrong scope
2085+
2086+
$coroutine = spawn with $scope {return;};
2087+
2088+
suspend;
2089+
$taskGroup->add($coroutine); // <- Exception: Task already completed
2090+
2091+
$coroutine = spawn with $scope {return;};
2092+
$taskGroup->add($coroutine);
2093+
$taskGroup->add($coroutine); // <- Exception: Task already added
2094+
```
2095+
2096+
#### TaskGroup and Scope
2097+
2098+
The `TaskGroup` constructor always requires an explicit `Scope` to be defined.
2099+
This is done to make the developer consider how long the tasks in the `TaskGroup` should live.
2100+
2101+
`TaskGroup` ties its lifetime to that of the `Scope` and cannot outlive it.
2102+
As a result, a `TaskGroup` cannot "hang in the air" like zombie coroutines do.
2103+
2104+
`TaskGroup` does not increase the reference count of the `$scope` object and does not prevent it from being disposed.
2105+
When the `Scope` associated with the `TaskGroup` enters the disposal phase,
2106+
it first releases all tasks within the `TaskGroup`.
20672107

20682108
Combining `Scope` and `TaskGroup` helps implement the pattern of primary and secondary tasks,
20692109
where tasks in the `TaskGroup` are treated as explicit, primary tasks that must be monitored for completion.
@@ -2078,20 +2118,24 @@ use Async\TaskGroup;
20782118
function targetTask(int $i): void
20792119
{
20802120
spawn {
2081-
// subtask
2121+
// subtask should be added to the same scope
20822122
};
20832123
}
20842124

2085-
$scope = new Scope();
2086-
$taskGroup = new TaskGroup(scope: $scope, captureResults: true);
2125+
$scope = new Scope(); // <- $scope reference equals one.
2126+
// TaskGroup will be binded to the $scope
2127+
$taskGroup = new TaskGroup(scope: $scope, captureResults: true); // <- Scope reference equals one.
20872128

20882129
for($i = 0; $i < 10; $i++) {
20892130
$taskGroup->add(spawn with $scope targetTask($i));
20902131
}
20912132

20922133
try {
2134+
// wait for only the tasks that were added to the TaskGroup
20932135
$results = await $taskGroup;
20942136
} finally {
2137+
// First dispose the task group
2138+
// then dispose the scope
20952139
$scope->dispose();
20962140
}
20972141
```
@@ -2176,7 +2220,8 @@ with the only difference being that it allows you to pass a specific exception.
21762220
```php
21772221
use Async\TaskGroup;
21782222

2179-
$taskGroup = new Async\TaskGroup(captureResults: false);
2223+
$scope = new Async\Scope();
2224+
$taskGroup = new Async\TaskGroup(scope: $scope, captureResults: false);
21802225
$taskGroup->add(spawn {
21812226
try {
21822227
suspend;
@@ -2223,7 +2268,8 @@ Using the option `$nullOnFail`, you can specify that the results of failed
22232268
tasks should be filled with `NULL` instead.
22242269

22252270
```php
2226-
$taskGroup = new Async\TaskGroup(captureResults: true);
2271+
$scope = new Async\Scope();
2272+
$taskGroup = new Async\TaskGroup($scope, captureResults: true);
22272273
$taskGroup->add(spawn {return 'result 1';});
22282274
$taskGroup->add(spawn {throw new Exception('Error')});
22292275

examples/Async/TaskGroup.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
final class TaskGroup implements Awaitable
88
{
99
public function __construct(
10-
private ?Scope $scope = null,
10+
private Scope $scope,
1111
private bool $captureResults = false
1212
) {}
1313

0 commit comments

Comments
 (0)