Skip to content

Commit 28c7c8b

Browse files
committed
more docs
1 parent f0fe850 commit 28c7c8b

File tree

2 files changed

+295
-11
lines changed

2 files changed

+295
-11
lines changed

stdlib/public/Concurrency/DiscardingTaskGroup.swift

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,58 @@ import Swift
1515

1616
// ==== DiscardingTaskGroup ---------------------------------------------------
1717

18+
/// Starts a new scope that can contain a dynamic number of child tasks.
19+
///
20+
/// Unlike a ``TaskGroup``, the child tasks as well as their results are
21+
/// discarded as soon as the tasks complete. This prevents the discarding
22+
/// task group from accumulating many results waiting to be consumed, and is
23+
/// best applied in situations where the result of a child task is some form
24+
/// of side-effect.
25+
///
26+
/// A group waits for all of its child tasks
27+
/// to complete before it returns. Even cancelled tasks must run until
28+
/// completion before this function returns.
29+
/// Cancelled child tasks cooperatively react to cancellation and attempt
30+
/// to return as early as possible.
31+
/// After this function returns, the task group is always empty.
32+
///
33+
/// It is not possible to explicitly await completion of child-tasks,
34+
/// however the group will automatically await *all* child task completions
35+
/// before returning from this function:
36+
///
37+
/// ```
38+
/// await withDiscardingTaskGroup { group in
39+
/// group.addTask { /* slow-task */ }
40+
/// // slow-task executes...
41+
/// }
42+
/// // guaranteed that slow-task has completed and the group is empty & destroyed
43+
/// ```
44+
///
45+
/// Task Group Cancellation
46+
/// =======================
47+
///
48+
/// You can cancel a task group and all of its child tasks
49+
/// by calling the ``TaskGroup/cancelAll()`` method on the task group,
50+
/// or by canceling the task in which the group is running.
51+
///
52+
/// If you call `addTask(priority:operation:)` to create a new task in a canceled group,
53+
/// that task is immediately canceled after creation.
54+
/// Alternatively, you can call `asyncUnlessCancelled(priority:operation:)`,
55+
/// which doesn't create the task if the group has already been canceled
56+
/// Choosing between these two functions
57+
/// lets you control how to react to cancellation within a group:
58+
/// some child tasks need to run regardless of cancellation,
59+
/// but other tasks are better not even being created
60+
/// when you know they can't produce useful results.
61+
///
62+
/// Because the tasks you add to a group with this method are nonthrowing,
63+
/// those tasks can't respond to cancellation by throwing `CancellationError`.
64+
/// The tasks must handle cancellation in some other way,
65+
/// such as returning the work completed so far, returning an empty result, or returning `nil`.
66+
/// For tasks that need to handle cancellation by throwing an error,
67+
/// use the `withThrowingDiscardingTaskGroup(returning:body:)` method instead.
68+
///
69+
/// - SeeAlso: ``withThrowingDiscardingTaskGroup(returning:body:)
1870
@available(SwiftStdlib 5.8, *)
1971
@inlinable
2072
@_unsafeInheritExecutor
@@ -40,6 +92,46 @@ public func withDiscardingTaskGroup<GroupResult>(
4092
#endif
4193
}
4294

95+
/// A discarding group that contains dynamically created child tasks.
96+
///
97+
/// To create a discarding task group,
98+
/// call the ``withDiscardingTaskGroup(returning:body:)`` method.
99+
///
100+
/// Don't use a task group from outside the task where you created it.
101+
/// In most cases,
102+
/// the Swift type system prevents a task group from escaping like that
103+
/// because adding a child task to a task group is a mutating operation,
104+
/// and mutation operations can't be performed
105+
/// from a concurrent execution context like a child task.
106+
///
107+
/// ### Task execution order
108+
/// Tasks added to a task group execute concurrently, and may be scheduled in
109+
/// any order.
110+
///
111+
/// ### Discarding behavior
112+
/// A discarding task group eagerly discards and releases its child tasks as
113+
/// soon as they complete. This allows for the efficient releasing of memory used
114+
/// by those tasks, which are not retained for future `next()` calls, as would
115+
/// be the case with a ``TaskGroup``.
116+
///
117+
/// ### Cancellation behavior
118+
/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is
119+
/// invoked on it, or when the ``Task`` running this task group is cancelled.
120+
///
121+
/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is
122+
/// automatically propagated through all of its child-tasks (and their child
123+
/// tasks).
124+
///
125+
/// A cancelled task group can still keep adding tasks, however they will start
126+
/// being immediately cancelled, and may act accordingly to this. To avoid adding
127+
/// new tasks to an already cancelled task group, use ``addTaskUnlessCancelled(priority:body:)``
128+
/// rather than the plain ``addTask(priority:body:)`` which adds tasks unconditionally.
129+
///
130+
/// For information about the language-level concurrency model that `DiscardingTaskGroup` is part of,
131+
/// see [Concurrency][concurrency] in [The Swift Programming Language][tspl].
132+
///
133+
/// [concurrency]: https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html
134+
/// [tspl]: https://docs.swift.org/swift-book/
43135
///
44136
/// - SeeAlso: ``TaskGroup``
45137
/// - SeeAlso: ``ThrowingTaskGroup``
@@ -188,6 +280,97 @@ extension DiscardingTaskGroup: Sendable { }
188280

189281
// ==== ThrowingDiscardingTaskGroup -------------------------------------------
190282

283+
/// Starts a new scope that can contain a dynamic number of child tasks.
284+
///
285+
/// Unlike a ``ThrowingTaskGroup``, the child tasks as well as their results are
286+
/// discarded as soon as the tasks complete. This prevents the discarding
287+
/// task group from accumulating many results waiting to be consumed, and is
288+
/// best applied in situations where the result of a child task is some form
289+
/// of side-effect.
290+
///
291+
/// A group waits for all of its child tasks
292+
/// to complete before it returns. Even cancelled tasks must run until
293+
/// completion before this function returns.
294+
/// Cancelled child tasks cooperatively react to cancellation and attempt
295+
/// to return as early as possible.
296+
/// After this function returns, the task group is always empty.
297+
///
298+
/// It is not possible to explicitly await completion of child-tasks,
299+
/// however the group will automatically await *all* child task completions
300+
/// before returning from this function:
301+
///
302+
/// ```
303+
/// try await withThrowingDiscardingTaskGroup { group in
304+
/// group.addTask { /* slow-task */ }
305+
/// // slow-task executes...
306+
/// }
307+
/// // guaranteed that slow-task has completed and the group is empty & destroyed
308+
/// ```
309+
///
310+
/// Task Group Cancellation
311+
/// =======================
312+
///
313+
/// You can cancel a task group and all of its child tasks
314+
/// by calling the ``TaskGroup/cancelAll()`` method on the task group,
315+
/// or by canceling the task in which the group is running.
316+
///
317+
/// If you call `addTask(priority:operation:)` to create a new task in a canceled group,
318+
/// that task is immediately canceled after creation.
319+
/// Alternatively, you can call `asyncUnlessCancelled(priority:operation:)`,
320+
/// which doesn't create the task if the group has already been canceled
321+
/// Choosing between these two functions
322+
/// lets you control how to react to cancellation within a group:
323+
/// some child tasks need to run regardless of cancellation,
324+
/// but other tasks are better not even being created
325+
/// when you know they can't produce useful results.
326+
///
327+
/// Error Handling and Implicit Cancellation
328+
/// ========================================
329+
///
330+
/// Since it is not possible to explicitly await individual task completions,
331+
/// it is also not possible to "re-throw" an error thrown by one of the child
332+
/// tasks using the same pattern as one would in a ``ThrowingTaskGroup``:
333+
///
334+
/// ```
335+
/// // ThrowingTaskGroup, pattern not applicable to ThrowingDiscardingTaskGroup
336+
/// try await withThrowingTaskGroup { group in
337+
/// group.addTask { try boom() }
338+
/// try await group.next() // re-throws "boom"
339+
/// }
340+
/// ```
341+
///
342+
/// Since discarding task groups don't have access to `next()`, this pattern
343+
/// cannot be used.
344+
/// Instead,
345+
/// a *throwing discarding task group implicitly cancels itself whenever any
346+
/// of its child tasks throws*.
347+
///
348+
/// The *first error* thrown inside such task group
349+
/// is then retained and thrown
350+
/// out of the `withThrowingDiscardingTaskGroup` method when it returns.
351+
///
352+
/// ```
353+
/// try await withThrowingDiscardingTaskGroup() { group in
354+
/// group.addTask { try boom(1) }
355+
/// group.addTask { try boom(2, after: .seconds(5)) }
356+
/// group.addTask { try boom(3, after: .seconds(5)) }
357+
/// }
358+
/// ```
359+
///
360+
///
361+
///
362+
/// Generally, this suits the typical use-cases of a
363+
/// discarding task group well, however, if you wanted to prevent specific
364+
/// errors from cancelling the group
365+
///
366+
///
367+
///
368+
///
369+
/// Throwing an error in one of the child tasks of a task group
370+
/// doesn't immediately cancel the other tasks in that group.
371+
/// However,
372+
/// throwing out of the `body` of the `withThrowingTaskGroup` method does cancel
373+
/// the group, and all of its child tasks.
191374
@available(SwiftStdlib 5.8, *)
192375
@inlinable
193376
@_unsafeInheritExecutor
@@ -223,6 +406,51 @@ public func withThrowingDiscardingTaskGroup<GroupResult>(
223406
#endif
224407
}
225408

409+
410+
/// A throwing discarding group that contains dynamically created child tasks.
411+
///
412+
/// To create a discarding task group,
413+
/// call the ``withDiscardingTaskGroup(returning:body:)`` method.
414+
///
415+
/// Don't use a task group from outside the task where you created it.
416+
/// In most cases,
417+
/// the Swift type system prevents a task group from escaping like that
418+
/// because adding a child task to a task group is a mutating operation,
419+
/// and mutation operations can't be performed
420+
/// from a concurrent execution context like a child task.
421+
///
422+
/// ### Task execution order
423+
/// Tasks added to a task group execute concurrently, and may be scheduled in
424+
/// any order.
425+
///
426+
/// ### Discarding behavior
427+
/// A discarding task group eagerly discards and releases its child tasks as
428+
/// soon as they complete. This allows for the efficient releasing of memory used
429+
/// by those tasks, which are not retained for future `next()` calls, as would
430+
/// be the case with a ``TaskGroup``.
431+
///
432+
/// ### Cancellation behavior
433+
/// A task group becomes cancelled in one of two ways: when ``cancelAll()`` is
434+
/// invoked on it, or when the ``Task`` running this task group is cancelled.
435+
///
436+
/// Since a `TaskGroup` is a structured concurrency primitive, cancellation is
437+
/// automatically propagated through all of its child-tasks (and their child
438+
/// tasks).
439+
///
440+
/// A cancelled task group can still keep adding tasks, however they will start
441+
/// being immediately cancelled, and may act accordingly to this. To avoid adding
442+
/// new tasks to an already cancelled task group, use ``addTaskUnlessCancelled(priority:body:)``
443+
/// rather than the plain ``addTask(priority:body:)`` which adds tasks unconditionally.
444+
///
445+
/// For information about the language-level concurrency model that `DiscardingTaskGroup` is part of,
446+
/// see [Concurrency][concurrency] in [The Swift Programming Language][tspl].
447+
///
448+
/// [concurrency]: https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html
449+
/// [tspl]: https://docs.swift.org/swift-book/
450+
///
451+
/// - SeeAlso: ``TaskGroup``
452+
/// - SeeAlso: ``ThrowingTaskGroup``
453+
/// - SeeAlso: ``DiscardingTaskGroup``
226454
@available(SwiftStdlib 5.8, *)
227455
@frozen
228456
public struct ThrowingDiscardingTaskGroup<Failure: Error> {
@@ -326,4 +554,4 @@ func _taskGroupWaitAll<T>(
326554

327555
@available(SwiftStdlib 5.8, *) // FIXME: remove
328556
@_silgen_name("swift_taskGroup_isDiscardingResults")
329-
func _taskGroupIsDiscardingResults(group: Builtin.RawPointer) -> Bool
557+
func _taskGroupIsDiscardingResults(group: Builtin.RawPointer) -> Bool

0 commit comments

Comments
 (0)