forked from apple/swift-evolution
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Structured concurrency: corrections from pitch feedback
- Loading branch information
Showing
1 changed file
with
48 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -238,23 +238,24 @@ results as they become ready: | |
func chopVegetables() async throws -> [Vegetable] { | ||
// Create a task group where each child task produces a Vegetable. | ||
try await Task.withGroup(resultType: Vegetable.self) { group in | ||
var veggies: [Vegetable] = gatherRawVeggies() | ||
var rawVeggies: [Vegetable] = gatherRawVeggies() | ||
var choppedVeggies: [Vegetable] = [] | ||
|
||
// Create a new child task for each vegetable that needs to be | ||
// chopped. | ||
for i in veggies.indices { | ||
for v in rawVeggies { | ||
await group.add { | ||
return veggies[i].chopped() | ||
return v.chopped() | ||
} | ||
} | ||
|
||
// Wait for all of the chopping to complete, collecting the veggies into | ||
// the result array in whatever order they're ready. | ||
while let choppedVeggie = try await group.next() { | ||
veggies.append(choppedVeggie) | ||
choppedVeggies.append(choppedVeggie) | ||
} | ||
|
||
return veggies | ||
return choppedVeggies | ||
} | ||
} | ||
``` | ||
|
@@ -370,6 +371,43 @@ func chop(_ vegetable: Vegetable) async throws -> Vegetable { | |
|
||
Note also that no information is passed to the task about why it was cancelled. A task may be cancelled for many reasons, and additional reasons may accrue after the initial cancellation (for example, if the task fails to immediately exit, it may pass a deadline). The goal of cancellation is to allow tasks to be cancelled in a lightweight way, not to be a secondary method of inter-task communication. | ||
|
||
Task groups can also be cancelled, in order to trigger cancellation of any | ||
still-running child tasks and prevent the addition of further children. A | ||
`Task.Group` value can also be tested for cancellation by reading its | ||
`isCancelled` property. Looking back at the previous example: | ||
|
||
```swift | ||
func chopVegetables() async throws -> [Vegetable] { | ||
var veggies: [Vegetable] = [] | ||
|
||
try await Task.withGroup(resultType: Vegetable.self) { group in | ||
await group.add { | ||
group.cancel() // Cancel all work in the group | ||
This comment has been minimized.
Sorry, something went wrong. |
||
throw UnfortunateAccidentWithKnifeError() | ||
} | ||
await group.add { | ||
return try await chop(Onion()) // (2) | ||
} | ||
|
||
print(group.isCancelled) // prints false | ||
|
||
do { | ||
while let veggie = try await group.next() { // (3) | ||
This comment has been minimized.
Sorry, something went wrong.
ktoso
Collaborator
|
||
veggies.append(veggie) | ||
} | ||
} catch { | ||
print(group.isCancelled) // prints true now | ||
let added = await group.add { | ||
return try await chop(SweetPotato()) | ||
} | ||
print(added) // prints false, no child was added to the cancelled group | ||
This comment has been minimized.
Sorry, something went wrong. |
||
} | ||
} | ||
|
||
return veggies | ||
} | ||
``` | ||
|
||
### Access from synchronous functions | ||
|
||
As already shown in the above examples, it is possible to call functions that inspect the "current task" in order to check if it was e.g. cancelled. However, those functions are on purpose not `async` and _can_ be called from synchronous functions as well. | ||
|
@@ -734,7 +772,7 @@ extension Task { | |
} | ||
``` | ||
|
||
The rationale for the default value is that if running outside of the Task infrastructure, there is no way to impact the priority of a task if not using the task infrastructure after all. | ||
The rationale for the default value is that if running outside of the Task infrastructure, there is no way for the caller to impact the priority of any task. | ||
|
||
#### Task Groups | ||
|
||
|
@@ -767,12 +805,12 @@ extension Task { | |
/// - once the `withGroup` returns the group is guaranteed to be empty. | ||
/// - if the body throws: | ||
/// - all tasks remaining in the group will be automatically cancelled. | ||
static func withGroup<TaskResult, BodyResult>( | ||
static func withGroup<TaskResult, Return>( | ||
resultType: TaskResult.Type, | ||
startingChildTasksOn executor: ExecutorRef? = nil, | ||
returning returnType: BodyResult.Type = BodyResult.self, | ||
body: (inout Task.Group<TaskResult>) async throws -> BodyResult | ||
) async throws -> BodyResult { ... } | ||
returning returnType: Return.Type = Return.self, | ||
body: (inout Task.Group<TaskResult>) async throws -> Return | ||
) async throws -> Return { ... } | ||
|
||
/// A group of tasks, each of which produces a result of type `TaskResult`. | ||
struct Group<TaskResult> { | ||
|
do we actually actually allow this?
Today this won't compile, as according with this proposal because:
public mutating func cancel()
Actually, the implementation is safe to do from other threads/tasks so we can lift the mutating and make it work from the children. It feels a bit weird but can be done.
WDYT?
If we want to support this then we need to remove
mutating
fromcancelAll
minor, typo: the function is called
group.cancelAll()