Skip to content

Commit

Permalink
Structured concurrency: corrections from pitch feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jckarter committed Feb 12, 2021
1 parent 0edd159 commit ed2e934
Showing 1 changed file with 48 additions and 10 deletions.
58 changes: 48 additions & 10 deletions proposals/nnnn-structured-concurrency.md
Expand Up @@ -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
}
}
```
Expand Down Expand Up @@ -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.

Copy link
@ktoso

ktoso Feb 17, 2021

Collaborator

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 from cancelAll


minor, typo: the function is called group.cancelAll()

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.

Copy link
@ktoso

ktoso Feb 17, 2021

Collaborator

we're not referring to those (1-3) at all, remove them or add comments?

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.

Copy link
@ktoso

ktoso Feb 17, 2021

Collaborator

right the added/isCancelled semantics are explained well 👍

}
}

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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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> {
Expand Down

0 comments on commit ed2e934

Please sign in to comment.