@@ -15,6 +15,58 @@ import Swift
15
15
16
16
// ==== DiscardingTaskGroup ---------------------------------------------------
17
17
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:)
18
70
@available ( SwiftStdlib 5 . 8 , * )
19
71
@inlinable
20
72
@_unsafeInheritExecutor
@@ -40,6 +92,46 @@ public func withDiscardingTaskGroup<GroupResult>(
40
92
#endif
41
93
}
42
94
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/
43
135
///
44
136
/// - SeeAlso: ``TaskGroup``
45
137
/// - SeeAlso: ``ThrowingTaskGroup``
@@ -188,6 +280,97 @@ extension DiscardingTaskGroup: Sendable { }
188
280
189
281
// ==== ThrowingDiscardingTaskGroup -------------------------------------------
190
282
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.
191
374
@available ( SwiftStdlib 5 . 8 , * )
192
375
@inlinable
193
376
@_unsafeInheritExecutor
@@ -223,6 +406,51 @@ public func withThrowingDiscardingTaskGroup<GroupResult>(
223
406
#endif
224
407
}
225
408
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``
226
454
@available ( SwiftStdlib 5 . 8 , * )
227
455
@frozen
228
456
public struct ThrowingDiscardingTaskGroup < Failure: Error > {
@@ -326,4 +554,4 @@ func _taskGroupWaitAll<T>(
326
554
327
555
@available ( SwiftStdlib 5 . 8 , * ) // FIXME: remove
328
556
@_silgen_name ( " swift_taskGroup_isDiscardingResults " )
329
- func _taskGroupIsDiscardingResults( group: Builtin . RawPointer ) -> Bool
557
+ func _taskGroupIsDiscardingResults( group: Builtin . RawPointer ) -> Bool
0 commit comments