diff --git a/stdlib/public/Concurrency/Task+init.swift.gyb b/stdlib/public/Concurrency/Task+init.swift.gyb index 36a2e597e0be3..ec121e26fdbcd 100644 --- a/stdlib/public/Concurrency/Task+init.swift.gyb +++ b/stdlib/public/Concurrency/Task+init.swift.gyb @@ -253,12 +253,13 @@ extension Task where Failure == ${FAILURE_TYPE} { % end /// % if IS_DETACHED: - /// Don't use a detached unstructured task if it's possible - /// to model the operation using structured concurrency features like child tasks. - /// Child tasks inherit the parent task's priority and task-local storage, - /// and canceling a parent task automatically cancels all of its child tasks. - /// You need to handle these considerations manually with a detached task. + /// A detached `Task` will not inherit task priority or task-local storage + /// from a parent task. You will need to handle these considerations manually. % end + /// + /// Asynchronous operations modeled with `Task` are ["unstructured concurrency"](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Unstructured-Concurrency). + /// Among other concerns, an unstructured task will not inherit cancellation + /// from a parent task. /// /// You need to keep a reference to the task /// if you want to cancel it by calling the `Task.cancel()` method. diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 3ad1294e79ee5..8a36482f58bc0 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -30,9 +30,9 @@ import Swift /// you give up the ability /// to wait for that task's result or cancel the task. /// -/// To support operations on the current task, -/// which can be either a detached task or child task, -/// `Task` also exposes class methods like `yield()`. +/// To support operations on the current task, `Task` also exposes class methods +/// like `yield()`. The current task can be either an unstructured `Task`, a +/// detached unstructured `Task`, or a child task (an `async let` or a task group). /// Because these methods are asynchronous, /// they're always invoked as part of an existing task. /// @@ -77,6 +77,17 @@ import Swift /// This reflects the fact that a task can be canceled for many reasons, /// and additional reasons can accrue during the cancellation process. /// +/// An instance of `Task` becomes cancelled only when its ``Task/cancel()`` +/// method is explicitly invoked. No other mechanism can cancel a `Task`, +/// including any instance of `Task` created within the scope of another +/// cancelled task. To propagate cancellation from an enclosing task, provide a +/// cancellation handler via ``withTaskCancellationHandler(operation:onCancel:isolation:)``. +/// +/// Any instance of `Task` that you initialize for yourself is a top-level, +/// _unstructured_ task. For more information about the implications of +/// unstructured tasks, read the ["Unstructured Concurrency"](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/#Unstructured-Concurrency) +/// portion of the Swift Programming Language book. +/// /// ### Task closure lifetime /// Tasks are initialized by passing a closure containing the code that will be executed by a given task. /// @@ -200,9 +211,15 @@ extension Task { /// /// - It flags the task as canceled. /// - It causes any active cancellation handlers on the task to run, once. - /// - It cancels any child tasks and task groups of the task, including - /// those created in the future. If those tasks have cancellation handlers, - /// they also are triggered. + /// - It cancels any child tasks (`async let` and other forms of structured + /// concurrency, not other `Task` instances) and task groups of the task, + /// including those created in the future. If those tasks have cancellation + /// handlers, they also are triggered. + /// + /// Cancelling a `Task` does not cancel other instances of `Task` that were + /// or will be created during its operation. Those other instances will not + /// become cancelled until and unless they are explicitly cancelled via + /// their `cancel()` methods being called on them. /// /// Task cancellation is cooperative and idempotent. /// @@ -671,8 +688,8 @@ extension Task where Success == Never, Failure == Never { /// and save it for long-term use. /// To query the current task without saving a reference to it, /// use properties like `currentPriority`. -/// If you need to store a reference to a task, -/// create an unstructured task using `Task.detached(priority:operation:)` instead. +/// If you need to store a reference to a task, create an unstructured task using +/// ``Task.init(name:priority:operation:)`` or ``Task.detached(priority:operation:)`` instead. /// /// - Parameters: /// - body: A closure that takes an `UnsafeCurrentTask` parameter. diff --git a/stdlib/public/Concurrency/TaskCancellation.swift b/stdlib/public/Concurrency/TaskCancellation.swift index b4d453e72651c..d8c584fade243 100644 --- a/stdlib/public/Concurrency/TaskCancellation.swift +++ b/stdlib/public/Concurrency/TaskCancellation.swift @@ -73,6 +73,33 @@ import Swift /// as resuming a continuation, may acquire these same internal locks. /// Therefore, if a cancellation handler must acquire a lock, other code should /// not cancel tasks or resume continuations while holding that lock. +/// +/// ### Propagating cancellation to unstructured tasks +/// +/// If an unstructured task (an instance of ``Task``) is created during the +/// operation of another task, cancellation of the enclosing task will not +/// propagate to the unstructured task without an explicit cancellation handler: +/// +/// ```swift +/// let outer = Task { +/// let inner = Task { +/// // The next statement will not throw an error unless cancellation of +/// // the `outer` task is manually propagated to the `inner` task via a +/// // cancellation handler: +/// try Task.checkCancellation() +/// return try await work() +/// } +/// return try await withTaskCancellationHandler { +/// try await inner.value +/// } onCancel: { +/// inner.cancel() +/// } +/// } +/// +/// // When `outer` becomes cancelled, the `onCancel` handler above will run, +/// // causing `inner` to become cancelled, too. +/// outer.cancel() +/// ``` @available(SwiftStdlib 5.1, *) #if !$Embedded @backDeployed(before: SwiftStdlib 6.0)