Skip to content

Commit be4caa5

Browse files
authored
[Docs][Concurrency] Fix documentation about cancellation, copy docs to a few methods (#63960)
1 parent 3ffb339 commit be4caa5

File tree

2 files changed

+139
-37
lines changed

2 files changed

+139
-37
lines changed

stdlib/public/Concurrency/DiscardingTaskGroup.swift

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,12 @@ public func withDiscardingTaskGroup<GroupResult>(
116116
/// be the case with a ``TaskGroup``.
117117
///
118118
/// ### Cancellation behavior
119-
/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is
120-
/// invoked on it, or when the ``Task`` running this task group is cancelled.
119+
/// A discarding task group becomes cancelled in one of the following ways:
121120
///
122-
/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is
121+
/// - when ``cancelAll()`` is invoked on it,
122+
/// - when the ``Task`` running this task group is cancelled.
123+
///
124+
/// Since a `DiscardingTaskGroup` is a structured concurrency primitive, cancellation is
123125
/// automatically propagated through all of its child-tasks (and their child
124126
/// tasks).
125127
///
@@ -158,6 +160,13 @@ public struct DiscardingTaskGroup {
158160
let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: nil)
159161
}
160162

163+
/// Adds a child task to the group.
164+
///
165+
/// - Parameters:
166+
/// - priority: The priority of the operation task.
167+
/// Omit this parameter or pass `.unspecified`
168+
/// to set the child task's priority to the priority of the group.
169+
/// - operation: The operation to execute as part of the task group.
161170
@_alwaysEmitIntoClient
162171
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
163172
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
@@ -184,6 +193,15 @@ public struct DiscardingTaskGroup {
184193
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
185194
}
186195

196+
/// Adds a child task to the group, unless the group has been canceled.
197+
///
198+
/// - Parameters:
199+
/// - priority: The priority of the operation task.
200+
/// Omit this parameter or pass `.unspecified`
201+
/// to set the child task's priority to the priority of the group.
202+
/// - operation: The operation to execute as part of the task group.
203+
/// - Returns: `true` if the child task was added to the group;
204+
/// otherwise `false`.
187205
@_alwaysEmitIntoClient
188206
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
189207
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
@@ -232,6 +250,12 @@ public struct DiscardingTaskGroup {
232250
_ = Builtin.createAsyncTaskInGroup(flags, _group, operation)
233251
}
234252

253+
/// Adds a child task to the group, unless the group has been canceled.
254+
///
255+
/// - Parameters:
256+
/// - operation: The operation to execute as part of the task group.
257+
/// - Returns: `true` if the child task was added to the group;
258+
/// otherwise `false`.
235259
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
236260
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTaskUnlessCancelled(operation:)")
237261
#endif
@@ -262,14 +286,46 @@ public struct DiscardingTaskGroup {
262286
#endif
263287
}
264288

289+
/// A Boolean value that indicates whether the group has any remaining tasks.
290+
///
291+
/// At the start of the body of a `withDiscardingTaskGroup(of:returning:body:)` call,
292+
/// the task group is always empty.
293+
///
294+
/// It's guaranteed to be empty when returning from that body
295+
/// because a task group waits for all child tasks to complete before returning.
296+
///
297+
/// - Returns: `true` if the group has no pending tasks; otherwise `false`.
265298
public var isEmpty: Bool {
266299
_taskGroupIsEmpty(_group)
267300
}
268301

302+
/// Cancel all of the remaining tasks in the group.
303+
///
304+
/// If you add a task to a group after canceling the group,
305+
/// that task is canceled immediately after being added to the group.
306+
///
307+
/// Immediately cancelled child tasks should therefore cooperatively check for and
308+
/// react to cancellation, e.g. by throwing an `CancellationError` at their
309+
/// earliest convenience, or otherwise handling the cancellation.
310+
///
311+
/// There are no restrictions on where you can call this method.
312+
/// Code inside a child task or even another task can cancel a group,
313+
/// however one should be very careful to not keep a reference to the
314+
/// group longer than the `with...TaskGroup(...) { ... }` method body is executing.
315+
///
316+
/// - SeeAlso: `Task.isCancelled`
317+
/// - SeeAlso: `DiscardingTaskGroup.isCancelled`
269318
public func cancelAll() {
270319
_taskGroupCancelAll(group: _group)
271320
}
272321

322+
/// A Boolean value that indicates whether the group was canceled.
323+
///
324+
/// To cancel a group, call the `DiscardingTaskGroup.cancelAll()` method.
325+
///
326+
/// If the task that's currently running this group is canceled,
327+
/// the group is also implicitly canceled,
328+
/// which is also reflected in this property's value.
273329
public var isCancelled: Bool {
274330
return _taskGroupIsCancelled(group: _group)
275331
}
@@ -431,10 +487,26 @@ public func withThrowingDiscardingTaskGroup<GroupResult>(
431487
/// be the case with a ``TaskGroup``.
432488
///
433489
/// ### Cancellation behavior
434-
/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is
435-
/// invoked on it, or when the ``Task`` running this task group is cancelled.
436-
///
437-
/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is
490+
/// A throwing discarding task group becomes cancelled in one of the following ways:
491+
///
492+
/// - when ``cancelAll()`` is invoked on it,
493+
/// - when an error is thrown out of the `withThrowingDiscardingTaskGroup(...) { }` closure,
494+
/// - when the ``Task`` running this task group is cancelled.
495+
///
496+
/// But also, and uniquely in *discarding* task groups:
497+
/// - when *any* of its child tasks throws.
498+
///
499+
/// The group becoming cancelled automatically, and cancelling all of its child tasks,
500+
/// whenever *any* child task throws an error is a behavior unique to discarding task groups,
501+
/// because achieving such semantics is not possible otherwise, due to the missing `next()` method
502+
/// on discarding groups. Accumulating task groups can implement this by manually polling `next()`
503+
/// and deciding to `cancelAll()` when they decide an error should cause the group to become cancelled,
504+
/// however a discarding group cannot poll child tasks for results and therefore assumes that child
505+
/// task throws are an indication of a group wide failure. In order to avoid such behavior,
506+
/// use a ``DiscardingTaskGroup`` instead of a throwing one, or catch specific errors in
507+
/// operations submitted using `addTask`
508+
///
509+
/// Since a `ThrowingDiscardingTaskGroup` is a structured concurrency primitive, cancellation is
438510
/// automatically propagated through all of its child-tasks (and their child
439511
/// tasks).
440512
///
@@ -524,14 +596,46 @@ public struct ThrowingDiscardingTaskGroup<Failure: Error> {
524596
#endif
525597
}
526598

599+
/// A Boolean value that indicates whether the group has any remaining tasks.
600+
///
601+
/// At the start of the body of a `withThrowingDiscardingTaskGroup(of:returning:body:)` call,
602+
/// the task group is always empty.
603+
///
604+
/// It's guaranteed to be empty when returning from that body
605+
/// because a task group waits for all child tasks to complete before returning.
606+
///
607+
/// - Returns: `true` if the group has no pending tasks; otherwise `false`.
527608
public var isEmpty: Bool {
528609
_taskGroupIsEmpty(_group)
529610
}
530611

612+
/// Cancel all of the remaining tasks in the group.
613+
///
614+
/// If you add a task to a group after canceling the group,
615+
/// that task is canceled immediately after being added to the group.
616+
///
617+
/// Immediately cancelled child tasks should therefore cooperatively check for and
618+
/// react to cancellation, e.g. by throwing an `CancellationError` at their
619+
/// earliest convenience, or otherwise handling the cancellation.
620+
///
621+
/// There are no restrictions on where you can call this method.
622+
/// Code inside a child task or even another task can cancel a group,
623+
/// however one should be very careful to not keep a reference to the
624+
/// group longer than the `with...TaskGroup(...) { ... }` method body is executing.
625+
///
626+
/// - SeeAlso: `Task.isCancelled`
627+
/// - SeeAlso: `ThrowingDiscardingTaskGroup.isCancelled`
531628
public func cancelAll() {
532629
_taskGroupCancelAll(group: _group)
533630
}
534631

632+
/// A Boolean value that indicates whether the group was canceled.
633+
///
634+
/// To cancel a group, call the `ThrowingDiscardingTaskGroup.cancelAll()` method.
635+
///
636+
/// If the task that's currently running this group is canceled,
637+
/// the group is also implicitly canceled,
638+
/// which is also reflected in this property's value.
535639
public var isCancelled: Bool {
536640
return _taskGroupIsCancelled(group: _group)
537641
}

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,11 @@ public func withThrowingTaskGroup<ChildTaskResult, GroupResult>(
218218
/// Tasks added to a task group execute concurrently, and may be scheduled in
219219
/// any order.
220220
///
221-
/// ### Discarding behavior
222-
/// A discarding task group eagerly discards and releases its child tasks as
223-
/// soon as they complete. This allows for the efficient releasing of memory used
224-
/// by those tasks, which are not retained for future `next()` calls, as would
225-
/// be the case with a ``TaskGroup``.
226-
///
227221
/// ### Cancellation behavior
228-
/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is
229-
/// invoked on it, or when the ``Task`` running this task group is cancelled.
222+
/// A task group becomes cancelled in one of the following ways:
223+
///
224+
/// - when ``cancelAll()`` is invoked on it,
225+
/// - when the ``Task`` running this task group is cancelled.
230226
///
231227
/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is
232228
/// automatically propagated through all of its child-tasks (and their child
@@ -262,7 +258,7 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
262258
/// Adds a child task to the group.
263259
///
264260
/// - Parameters:
265-
/// - overridingPriority: The priority of the operation task.
261+
/// - priority: The priority of the operation task.
266262
/// Omit this parameter or pass `.unspecified`
267263
/// to set the child task's priority to the priority of the group.
268264
/// - operation: The operation to execute as part of the task group.
@@ -336,7 +332,7 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
336332
fatalError("Unsupported Swift compiler")
337333
#endif
338334
}
339-
#else
335+
#else // if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
340336
@available(SwiftStdlib 5.7, *)
341337
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")
342338
public mutating func addTask(
@@ -491,15 +487,17 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
491487

492488
/// Cancel all of the remaining tasks in the group.
493489
///
494-
/// After cancellation,
495-
/// any new results from the tasks in this group
496-
/// are silently discarded.
497-
///
498490
/// If you add a task to a group after canceling the group,
499491
/// that task is canceled immediately after being added to the group.
500492
///
501-
/// This method can only be called by the parent task that created the task
502-
/// group.
493+
/// Immediately cancelled child tasks should therefore cooperatively check for and
494+
/// react to cancellation, e.g. by throwing an `CancellationError` at their
495+
/// earliest convenience, or otherwise handling the cancellation.
496+
///
497+
/// There are no restrictions on where you can call this method.
498+
/// Code inside a child task or even another task can cancel a group,
499+
/// however one should be very careful to not keep a reference to the
500+
/// group longer than the `with...TaskGroup(...) { ... }` method body is executing.
503501
///
504502
/// - SeeAlso: `Task.isCancelled`
505503
/// - SeeAlso: `TaskGroup.isCancelled`
@@ -558,17 +556,14 @@ extension TaskGroup: Sendable { }
558556
/// Tasks added to a task group execute concurrently, and may be scheduled in
559557
/// any order.
560558
///
561-
/// ### Discarding behavior
562-
/// A discarding task group eagerly discards and releases its child tasks as
563-
/// soon as they complete. This allows for the efficient releasing of memory used
564-
/// by those tasks, which are not retained for future `next()` calls, as would
565-
/// be the case with a ``TaskGroup``.
566-
///
567559
/// ### Cancellation behavior
568-
/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is
569-
/// invoked on it, or when the ``Task`` running this task group is cancelled.
560+
/// A task group becomes cancelled in one of the following ways:
570561
///
571-
/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is
562+
/// - when ``cancelAll()`` is invoked on it,
563+
/// - when an error is thrown out of the `withThrowingTaskGroup(...) { }` closure,
564+
/// - when the ``Task`` running this task group is cancelled.
565+
///
566+
/// Since a `ThrowingTaskGroup` is a structured concurrency primitive, cancellation is
572567
/// automatically propagated through all of its child-tasks (and their child
573568
/// tasks).
574569
///
@@ -935,6 +930,7 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
935930
///
936931
/// At the start of the body of a `withThrowingTaskGroup(of:returning:body:)` call,
937932
/// the task group is always empty.
933+
///
938934
/// It's guaranteed to be empty when returning from that body
939935
/// because a task group waits for all child tasks to complete before returning.
940936
///
@@ -945,15 +941,17 @@ public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
945941

946942
/// Cancel all of the remaining tasks in the group.
947943
///
948-
/// After cancellation,
949-
/// any new results or errors from the tasks in this group
950-
/// are silently discarded.
951-
///
952944
/// If you add a task to a group after canceling the group,
953945
/// that task is canceled immediately after being added to the group.
954946
///
947+
/// Immediately cancelled child tasks should therefore cooperatively check for and
948+
/// react to cancellation, e.g. by throwing an `CancellationError` at their
949+
/// earliest convenience, or otherwise handling the cancellation.
950+
///
955951
/// There are no restrictions on where you can call this method.
956-
/// Code inside a child task or even another task can cancel a group.
952+
/// Code inside a child task or even another task can cancel a group,
953+
/// however one should be very careful to not keep a reference to the
954+
/// group longer than the `with...TaskGroup(...) { ... }` method body is executing.
957955
///
958956
/// - SeeAlso: `Task.isCancelled`
959957
/// - SeeAlso: `ThrowingTaskGroup.isCancelled`

0 commit comments

Comments
 (0)