diff --git a/stdlib/public/Concurrency/DiscardingTaskGroup.swift b/stdlib/public/Concurrency/DiscardingTaskGroup.swift index 1b6db94c02fe1..e7d8987e05236 100644 --- a/stdlib/public/Concurrency/DiscardingTaskGroup.swift +++ b/stdlib/public/Concurrency/DiscardingTaskGroup.swift @@ -116,10 +116,12 @@ public func withDiscardingTaskGroup( /// be the case with a ``TaskGroup``. /// /// ### Cancellation behavior -/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is -/// invoked on it, or when the ``Task`` running this task group is cancelled. +/// A discarding task group becomes cancelled in one of the following ways: /// -/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is +/// - when ``cancelAll()`` is invoked on it, +/// - when the ``Task`` running this task group is cancelled. +/// +/// Since a `DiscardingTaskGroup` is a structured concurrency primitive, cancellation is /// automatically propagated through all of its child-tasks (and their child /// tasks). /// @@ -158,6 +160,13 @@ public struct DiscardingTaskGroup { let _: Void? = try await _taskGroupWaitAll(group: _group, bodyError: nil) } + /// Adds a child task to the group. + /// + /// - Parameters: + /// - priority: The priority of the operation task. + /// Omit this parameter or pass `.unspecified` + /// to set the child task's priority to the priority of the group. + /// - operation: The operation to execute as part of the task group. @_alwaysEmitIntoClient #if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)") @@ -184,6 +193,15 @@ public struct DiscardingTaskGroup { _ = Builtin.createAsyncTaskInGroup(flags, _group, operation) } + /// Adds a child task to the group, unless the group has been canceled. + /// + /// - Parameters: + /// - priority: The priority of the operation task. + /// Omit this parameter or pass `.unspecified` + /// to set the child task's priority to the priority of the group. + /// - operation: The operation to execute as part of the task group. + /// - Returns: `true` if the child task was added to the group; + /// otherwise `false`. @_alwaysEmitIntoClient #if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)") @@ -232,6 +250,12 @@ public struct DiscardingTaskGroup { _ = Builtin.createAsyncTaskInGroup(flags, _group, operation) } + /// Adds a child task to the group, unless the group has been canceled. + /// + /// - Parameters: + /// - operation: The operation to execute as part of the task group. + /// - Returns: `true` if the child task was added to the group; + /// otherwise `false`. #if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTaskUnlessCancelled(operation:)") #endif @@ -262,14 +286,46 @@ public struct DiscardingTaskGroup { #endif } + /// A Boolean value that indicates whether the group has any remaining tasks. + /// + /// At the start of the body of a `withDiscardingTaskGroup(of:returning:body:)` call, + /// the task group is always empty. + /// + /// It's guaranteed to be empty when returning from that body + /// because a task group waits for all child tasks to complete before returning. + /// + /// - Returns: `true` if the group has no pending tasks; otherwise `false`. public var isEmpty: Bool { _taskGroupIsEmpty(_group) } + /// Cancel all of the remaining tasks in the group. + /// + /// If you add a task to a group after canceling the group, + /// that task is canceled immediately after being added to the group. + /// + /// Immediately cancelled child tasks should therefore cooperatively check for and + /// react to cancellation, e.g. by throwing an `CancellationError` at their + /// earliest convenience, or otherwise handling the cancellation. + /// + /// There are no restrictions on where you can call this method. + /// Code inside a child task or even another task can cancel a group, + /// however one should be very careful to not keep a reference to the + /// group longer than the `with...TaskGroup(...) { ... }` method body is executing. + /// + /// - SeeAlso: `Task.isCancelled` + /// - SeeAlso: `DiscardingTaskGroup.isCancelled` public func cancelAll() { _taskGroupCancelAll(group: _group) } + /// A Boolean value that indicates whether the group was canceled. + /// + /// To cancel a group, call the `DiscardingTaskGroup.cancelAll()` method. + /// + /// If the task that's currently running this group is canceled, + /// the group is also implicitly canceled, + /// which is also reflected in this property's value. public var isCancelled: Bool { return _taskGroupIsCancelled(group: _group) } @@ -431,10 +487,26 @@ public func withThrowingDiscardingTaskGroup( /// be the case with a ``TaskGroup``. /// /// ### Cancellation behavior -/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is -/// invoked on it, or when the ``Task`` running this task group is cancelled. -/// -/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is +/// A throwing discarding task group becomes cancelled in one of the following ways: +/// +/// - when ``cancelAll()`` is invoked on it, +/// - when an error is thrown out of the `withThrowingDiscardingTaskGroup(...) { }` closure, +/// - when the ``Task`` running this task group is cancelled. +/// +/// But also, and uniquely in *discarding* task groups: +/// - when *any* of its child tasks throws. +/// +/// The group becoming cancelled automatically, and cancelling all of its child tasks, +/// whenever *any* child task throws an error is a behavior unique to discarding task groups, +/// because achieving such semantics is not possible otherwise, due to the missing `next()` method +/// on discarding groups. Accumulating task groups can implement this by manually polling `next()` +/// and deciding to `cancelAll()` when they decide an error should cause the group to become cancelled, +/// however a discarding group cannot poll child tasks for results and therefore assumes that child +/// task throws are an indication of a group wide failure. In order to avoid such behavior, +/// use a ``DiscardingTaskGroup`` instead of a throwing one, or catch specific errors in +/// operations submitted using `addTask` +/// +/// Since a `ThrowingDiscardingTaskGroup` is a structured concurrency primitive, cancellation is /// automatically propagated through all of its child-tasks (and their child /// tasks). /// @@ -524,14 +596,46 @@ public struct ThrowingDiscardingTaskGroup { #endif } + /// A Boolean value that indicates whether the group has any remaining tasks. + /// + /// At the start of the body of a `withThrowingDiscardingTaskGroup(of:returning:body:)` call, + /// the task group is always empty. + /// + /// It's guaranteed to be empty when returning from that body + /// because a task group waits for all child tasks to complete before returning. + /// + /// - Returns: `true` if the group has no pending tasks; otherwise `false`. public var isEmpty: Bool { _taskGroupIsEmpty(_group) } + /// Cancel all of the remaining tasks in the group. + /// + /// If you add a task to a group after canceling the group, + /// that task is canceled immediately after being added to the group. + /// + /// Immediately cancelled child tasks should therefore cooperatively check for and + /// react to cancellation, e.g. by throwing an `CancellationError` at their + /// earliest convenience, or otherwise handling the cancellation. + /// + /// There are no restrictions on where you can call this method. + /// Code inside a child task or even another task can cancel a group, + /// however one should be very careful to not keep a reference to the + /// group longer than the `with...TaskGroup(...) { ... }` method body is executing. + /// + /// - SeeAlso: `Task.isCancelled` + /// - SeeAlso: `ThrowingDiscardingTaskGroup.isCancelled` public func cancelAll() { _taskGroupCancelAll(group: _group) } + /// A Boolean value that indicates whether the group was canceled. + /// + /// To cancel a group, call the `ThrowingDiscardingTaskGroup.cancelAll()` method. + /// + /// If the task that's currently running this group is canceled, + /// the group is also implicitly canceled, + /// which is also reflected in this property's value. public var isCancelled: Bool { return _taskGroupIsCancelled(group: _group) } diff --git a/stdlib/public/Concurrency/TaskGroup.swift b/stdlib/public/Concurrency/TaskGroup.swift index a0912454e9f2e..654be11be9a0f 100644 --- a/stdlib/public/Concurrency/TaskGroup.swift +++ b/stdlib/public/Concurrency/TaskGroup.swift @@ -218,15 +218,11 @@ public func withThrowingTaskGroup( /// Tasks added to a task group execute concurrently, and may be scheduled in /// any order. /// -/// ### Discarding behavior -/// A discarding task group eagerly discards and releases its child tasks as -/// soon as they complete. This allows for the efficient releasing of memory used -/// by those tasks, which are not retained for future `next()` calls, as would -/// be the case with a ``TaskGroup``. -/// /// ### Cancellation behavior -/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is -/// invoked on it, or when the ``Task`` running this task group is cancelled. +/// A task group becomes cancelled in one of the following ways: +/// +/// - when ``cancelAll()`` is invoked on it, +/// - when the ``Task`` running this task group is cancelled. /// /// Since a `TaskGroup` is a structured concurrency primitive, cancellation is /// automatically propagated through all of its child-tasks (and their child @@ -262,7 +258,7 @@ public struct TaskGroup { /// Adds a child task to the group. /// /// - Parameters: - /// - overridingPriority: The priority of the operation task. + /// - priority: The priority of the operation task. /// Omit this parameter or pass `.unspecified` /// to set the child task's priority to the priority of the group. /// - operation: The operation to execute as part of the task group. @@ -336,7 +332,7 @@ public struct TaskGroup { fatalError("Unsupported Swift compiler") #endif } -#else +#else // if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.7, *) @available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)") public mutating func addTask( @@ -491,15 +487,17 @@ public struct TaskGroup { /// Cancel all of the remaining tasks in the group. /// - /// After cancellation, - /// any new results from the tasks in this group - /// are silently discarded. - /// /// If you add a task to a group after canceling the group, /// that task is canceled immediately after being added to the group. /// - /// This method can only be called by the parent task that created the task - /// group. + /// Immediately cancelled child tasks should therefore cooperatively check for and + /// react to cancellation, e.g. by throwing an `CancellationError` at their + /// earliest convenience, or otherwise handling the cancellation. + /// + /// There are no restrictions on where you can call this method. + /// Code inside a child task or even another task can cancel a group, + /// however one should be very careful to not keep a reference to the + /// group longer than the `with...TaskGroup(...) { ... }` method body is executing. /// /// - SeeAlso: `Task.isCancelled` /// - SeeAlso: `TaskGroup.isCancelled` @@ -558,17 +556,14 @@ extension TaskGroup: Sendable { } /// Tasks added to a task group execute concurrently, and may be scheduled in /// any order. /// -/// ### Discarding behavior -/// A discarding task group eagerly discards and releases its child tasks as -/// soon as they complete. This allows for the efficient releasing of memory used -/// by those tasks, which are not retained for future `next()` calls, as would -/// be the case with a ``TaskGroup``. -/// /// ### Cancellation behavior -/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is -/// invoked on it, or when the ``Task`` running this task group is cancelled. +/// A task group becomes cancelled in one of the following ways: /// -/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is +/// - when ``cancelAll()`` is invoked on it, +/// - when an error is thrown out of the `withThrowingTaskGroup(...) { }` closure, +/// - when the ``Task`` running this task group is cancelled. +/// +/// Since a `ThrowingTaskGroup` is a structured concurrency primitive, cancellation is /// automatically propagated through all of its child-tasks (and their child /// tasks). /// @@ -906,6 +901,7 @@ public struct ThrowingTaskGroup { /// /// At the start of the body of a `withThrowingTaskGroup(of:returning:body:)` call, /// the task group is always empty. + /// /// It's guaranteed to be empty when returning from that body /// because a task group waits for all child tasks to complete before returning. /// @@ -916,15 +912,17 @@ public struct ThrowingTaskGroup { /// Cancel all of the remaining tasks in the group. /// - /// After cancellation, - /// any new results or errors from the tasks in this group - /// are silently discarded. - /// /// If you add a task to a group after canceling the group, /// that task is canceled immediately after being added to the group. /// + /// Immediately cancelled child tasks should therefore cooperatively check for and + /// react to cancellation, e.g. by throwing an `CancellationError` at their + /// earliest convenience, or otherwise handling the cancellation. + /// /// There are no restrictions on where you can call this method. - /// Code inside a child task or even another task can cancel a group. + /// Code inside a child task or even another task can cancel a group, + /// however one should be very careful to not keep a reference to the + /// group longer than the `with...TaskGroup(...) { ... }` method body is executing. /// /// - SeeAlso: `Task.isCancelled` /// - SeeAlso: `ThrowingTaskGroup.isCancelled`