@@ -1645,7 +1645,6 @@ function generateReport(): void
16451645 } catch (Exception $e) {
16461646 echo "Failed to generate report: ", $e->getMessage(), "\n";
16471647 } finally {
1648- $taskGroup->dispose();
16491648 $scope->disposeSafely();
16501649 }
16511650}
@@ -1900,7 +1899,7 @@ final class ProcessPool
19001899 $this->poolScope = new Scope();
19011900 $this->watcherScope = new Scope();
19021901 $this->jobsScope = new Scope();
1903- $this->taskGroup = new TaskGroup(captureResults: false);
1902+ $this->taskGroup = new TaskGroup($this->poolScope, captureResults: false);
19041903 }
19051904
19061905 public function __destruct()
@@ -2066,21 +2065,51 @@ that all coroutines launched within it should also be terminated.
20662065
20672066#### Combining TaskGroup and Scope
20682067
2069- ` TaskGroup ` is convenient to use in combination with ` Scope ` , if
2070- the ` $scope ` object belongs exclusively to the ` TaskGroup ` .
2071- In that case, when the ` TaskGroup ` goes out of scope,
2072- the ` Scope ` object and all associated tasks will be destroyed together.
2068+ Combining ` Scope ` and ` TaskGroup ` helps implement the pattern of primary and secondary tasks,
2069+ where tasks in the ` TaskGroup ` are treated as explicit, primary tasks that must be monitored for completion.
2070+ All other tasks that enter the ` Scope ` while the primary tasks are running are considered secondary.
2071+
2072+ The following code demonstrates this idea:
20732073
20742074``` php
2075- $scope = new Async\Scope();
2076- $taskGroup = new Async\TaskGroup(scope: $scope, captureResults: false); // <- Scope reference equals two.
2075+ use Async\Scope;
2076+ use Async\TaskGroup;
2077+
2078+ function targetTask(int $i): void
2079+ {
2080+ spawn {
2081+ // subtask
2082+ };
2083+ }
2084+
2085+ $scope = new Scope();
2086+ $taskGroup = new TaskGroup(scope: $scope, captureResults: true);
2087+
2088+ for($i = 0; $i < 10; $i++) {
2089+ $taskGroup->add(spawn with $scope targetTask($i));
2090+ }
2091+
2092+ try {
2093+ $results = await $taskGroup;
2094+ } finally {
2095+ $scope->dispose();
2096+ }
2097+ ```
2098+
2099+ Coroutines that can be created by ` targetTask ` will be placed into ` $scope ` .
2100+ However, only the tasks that are explicitly added to the ` TaskGroup ` will be awaited.
2101+ When ` $scope ` is cancelled, the ` TaskGroup ` will be disposed of along with it.
2102+
2103+ ``` php
2104+ $scope = new Async\Scope(); // <- $scope reference equals one.
2105+ $taskGroup = new Async\TaskGroup(scope: $scope, captureResults: false); // <- Scope reference equals one.
20772106$taskGroup- >add(spawn with $scope {
20782107 Async\delay(5000);
20792108 echo "This line will be executed\n";
20802109});
2081- unset($scope); // <- $scope reference equals one.
20822110sleep(1);
2083- $taskGroup- >dispose(); // <- $scope reference equals zero.
2111+ $scope->dispose(); // <- $scope reference equals zero.
2112+ // $taskGroup- >dispose() will be called before the $scope disposal.
20842113```
20852114
20862115** Expected output:**
@@ -2089,42 +2118,12 @@ $taskGroup->dispose(); // <- $scope reference equals zero.
20892118```
20902119
20912120There are no warnings about ** zombie coroutines** in the output
2092- because the task was canceled using `$taskGroup- >dispose()`, after which the `scope` was also destroyed .
2121+ because the task was canceled using ` $taskGroup->dispose() ` .
20932122
20942123However, if the ` Scope ` contains other coroutines that were created outside the ` TaskGroup ` ,
20952124they will follow the general rules. In the case of the ` Scope::disposeSafely() ` strategy,
20962125a warning will be issued if unfinished tasks are detected, as they would become ** zombie coroutines** .
20972126
2098- If you are certain that all tasks should be canceled, use the `bounded: true` option in the `TaskGroup` constructor.
2099- In this case, the `Scope` will be terminated using the `Scope::dispose()` strategy.
2100-
2101- ```php
2102- use Async\TaskGroup;
2103- use Async\Scope;
2104- use function Async\delay;
2105-
2106- $scope = new Scope();
2107- $taskGroup = new TaskGroup(scope: , captureResults: false, bounded: true);
2108-
2109- spawn with $scope { // <- it ' s a zombie coroutine, but it will be canceled because of the bounded task group
2110- delay(5000);
2111- echo "This line will be executed1\n";
2112- };
2113-
2114- $taskGroup->spawn(function() {
2115- delay(5000);
2116- echo "This line will be executed2\n";
2117- });
2118-
2119- sleep(1);
2120- $scope->dispose();
2121- ```
2122-
2123- **Expected output:**
2124-
2125- ```
2126- ```
2127-
21282127#### TaskGroup Race
21292128
21302129The ` TaskGroup ` class allows you to wait for the first task to complete using the ` race() ` method.
@@ -2134,7 +2133,8 @@ use Async\TaskGroup;
21342133
21352134function fetchFirstSuccessful(string ...$apiHosts): string
21362135{
2137- $taskGroup = new Async\TaskGroup(scope: new Async\Scope(), captureResults: false, bounded: true);
2136+ $scope = new Async\Scope();
2137+ $taskGroup = new Async\TaskGroup(scope: $scope, captureResults: false);
21382138
21392139 foreach ($apiHosts as $host) {
21402140 $taskGroup->spawn(function() use ($host) {
0 commit comments