diff --git a/stdlib/public/Concurrency/Task+init.swift.gyb b/stdlib/public/Concurrency/Task+init.swift.gyb index 36a2e597e0be3..836cb3fcb78d2 100644 --- a/stdlib/public/Concurrency/Task+init.swift.gyb +++ b/stdlib/public/Concurrency/Task+init.swift.gyb @@ -250,16 +250,19 @@ extension Task where Failure == ${FAILURE_TYPE} { /// If the `operation` throws an error, it is caught by the `Task` and will be /// rethrown only when the task's `value` is awaited. Take care to not accidentally /// dismiss errors by not awaiting on the task's resulting value. + /// % end + /// Asynchronous work modeled with `Task` is unstructured concurrency. + /// Don't use an unstructured task if it's possible to model the operation + /// using structured concurrency features like child tasks (such as `async let` + /// or task groups). Child tasks automatically become cancelled when their + /// parent task becomes cancelled. /// % 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. -% end + /// 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 /// You need to keep a reference to the task /// if you want to cancel it by calling the `Task.cancel()` method. /// Discarding your reference to a task diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index 3ad1294e79ee5..4fc3cfb9473af 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. /// @@ -47,6 +47,11 @@ import Swift /// Unless you're implementing a custom executor, /// you don't directly interact with partial tasks. /// +/// Asynchronous work modeled with `Task` is unstructured concurrency. +/// Don't use an unstructured task if it's possible to model the operation +/// using structured concurrency features like child tasks (such as `async let` +/// or task groups). +/// /// For information about the language-level concurrency model that `Task` is part of, /// see [Concurrency][concurrency] in [The Swift Programming Language][tspl]. /// @@ -77,6 +82,12 @@ 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:)``. +/// /// ### 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)