Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions stdlib/public/Concurrency/Task+init.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,14 @@ 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.
Comment on lines -256 to -257
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding was that this guidance still stands -- what's the reason to stop recommending against detached tasks?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right to point this out. In 824d47b I have reintroduced this guidance, but in such a way that it is no longer limited to the IS_DETACHED condition. This guidance is equally applicable to all unstructured tasks, so with my changes in 824d47b it should appear in the documentation unconditionally (it has been phrased so as not to mention "detached" specifically).

/// 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.
/// In most cases, an "attached" unstructured task (created via `Task.init`)
/// is a more appropriate choice than a detached unstructured task. Attached
/// tasks inherit the priority and task-local storage from the enclosing task
/// during which they are created. You need to handle these considerations
/// manually with a detached task.
///
/// A detached `Task` has the same cancellation mechanism as any other `Task`;
/// it can only become cancelled via an explicit `cancel()` method call.
% end
///
/// You need to keep a reference to the task
Expand Down
55 changes: 47 additions & 8 deletions stdlib/public/Concurrency/Task.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -77,6 +77,38 @@ import Swift
/// This reflects the fact that a task can be canceled for many reasons,
/// and additional reasons can accrue during the cancellation process.
///
/// A `Task` becomes cancelled when its `cancel()` method is called on it. There
/// is no other mechanism by which a `Task` can become cancelled. This is true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats very sneaky wording I’m not comfortable with… its true for specifically an unstructured task but the wording here isn’t clear enough, it may sound like you mean “any task”

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rephrased in 894c404.

/// even of a `Task` instance which is created inside the operation of another
/// `Task`. You can manually propagate cancellation from an enclosing `Task` to
/// another task created during its operation via `withTaskCancellationHandler(operation:onCancel:isolation:)`:
///
/// ```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()
/// ```
///
/// 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.
///
Expand Down Expand Up @@ -200,9 +232,16 @@ 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 (lowercase-t tasks, like `async let` and
/// other forms of structured concurrency, not capital-t `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. Read the "Task Cancellation"
/// section of the `Task` documentation for more information.
///
/// Task cancellation is cooperative and idempotent.
///
Expand Down Expand Up @@ -671,8 +710,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.
Expand Down