@@ -61,10 +61,8 @@ import Swift
61
61
/// - if the body returns normally:
62
62
/// - the group will await any not yet complete tasks,
63
63
/// - once the `withTaskGroup` returns the group is guaranteed to be empty.
64
- /// - if the body throws:
65
- /// - all tasks remaining in the group will be automatically cancelled.
66
64
@available ( macOS 9999 , iOS 9999 , watchOS 9999 , tvOS 9999 , * )
67
- public func withTaskGroup< ChildTaskResult, GroupResult> (
65
+ public func withTaskGroup< ChildTaskResult: Sendable , GroupResult> (
68
66
of childTaskResultType: ChildTaskResult . Type ,
69
67
returning returnType: GroupResult . Type = GroupResult . self,
70
68
body: ( inout TaskGroup < ChildTaskResult > ) async -> GroupResult
@@ -144,7 +142,7 @@ public func withTaskGroup<ChildTaskResult, GroupResult>(
144
142
/// - once the `withTaskGroup` returns the group is guaranteed to be empty.
145
143
/// - if the body throws:
146
144
/// - all tasks remaining in the group will be automatically cancelled.
147
- public func withThrowingTaskGroup< ChildTaskResult, GroupResult> (
145
+ public func withThrowingTaskGroup< ChildTaskResult: Sendable , GroupResult> (
148
146
of childTaskResultType: ChildTaskResult . Type ,
149
147
returning returnType: GroupResult . Type = GroupResult . self,
150
148
body: ( inout ThrowingTaskGroup < ChildTaskResult , Error > ) async throws -> GroupResult
@@ -191,7 +189,7 @@ public func withThrowingTaskGroup<ChildTaskResult, GroupResult>(
191
189
/// A task group serves as storage for dynamically spawned tasks.
192
190
///
193
191
/// It is created by the `withTaskGroup` function.
194
- public struct TaskGroup < ChildTaskResult> {
192
+ public struct TaskGroup < ChildTaskResult: Sendable > {
195
193
196
194
private let _task : Builtin . NativeObject
197
195
/// Group task into which child tasks offer their results,
@@ -225,13 +223,13 @@ public struct TaskGroup<ChildTaskResult> {
225
223
@discardableResult
226
224
public mutating func spawn(
227
225
overridingPriority priorityOverride: Task . Priority ? = nil ,
228
- operation: @Sendable @escaping ( ) async -> ChildTaskResult
229
- ) async -> Bool {
226
+ operation: __owned @Sendable @escaping ( ) async -> ChildTaskResult
227
+ ) -> Self . Spawned {
230
228
let canAdd = _taskGroupAddPendingTask ( group: _group)
231
229
232
230
guard canAdd else {
233
231
// the group is cancelled and is not accepting any new work
234
- return false
232
+ return Spawned ( handle : nil )
235
233
}
236
234
237
235
// Set up the job flags for a new task.
@@ -252,7 +250,22 @@ public struct TaskGroup<ChildTaskResult> {
252
250
// Enqueue the resulting job.
253
251
_enqueueJobGlobal ( Builtin . convertTaskToJob ( childTask) )
254
252
255
- return true
253
+ return Spawned ( handle: Task . Handle ( childTask) )
254
+ }
255
+
256
+ public struct Spawned : Sendable {
257
+ /// Returns `true` if the task was successfully spawned in the task group,
258
+ /// `false` otherwise which means that the group was already cancelled and
259
+ /// refused to accept spawn a new child task.
260
+ public var successfully : Bool { handle != nil }
261
+
262
+ /// Task handle for the spawned task group child task,
263
+ /// or `nil` if it was not spawned successfully.
264
+ public let handle : Task . Handle < ChildTaskResult , Never > ?
265
+
266
+ init ( handle: Task . Handle < ChildTaskResult , Never > ? ) {
267
+ self . handle = handle
268
+ }
256
269
}
257
270
258
271
/// Wait for the a child task that was added to the group to complete,
@@ -296,8 +309,8 @@ public struct TaskGroup<ChildTaskResult> {
296
309
/// Order of values returned by next() is *completion order*, and not
297
310
/// submission order. I.e. if tasks are added to the group one after another:
298
311
///
299
- /// await group.spawn { 1 }
300
- /// await group.spawn { 2 }
312
+ /// group.spawn { 1 }
313
+ /// group.spawn { 2 }
301
314
///
302
315
/// print(await group.next())
303
316
/// /// Prints "1" OR "2"
@@ -388,7 +401,7 @@ public struct TaskGroup<ChildTaskResult> {
388
401
/// child tasks.
389
402
///
390
403
/// It is created by the `withTaskGroup` function.
391
- public struct ThrowingTaskGroup < ChildTaskResult, Failure: Error > {
404
+ public struct ThrowingTaskGroup < ChildTaskResult: Sendable , Failure: Error > {
392
405
393
406
private let _task : Builtin . NativeObject
394
407
/// Group task into which child tasks offer their results,
@@ -423,12 +436,12 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
423
436
public mutating func spawn(
424
437
overridingPriority priorityOverride: Task . Priority ? = nil ,
425
438
operation: __owned @Sendable @escaping ( ) async throws -> ChildTaskResult
426
- ) async -> Bool {
439
+ ) -> Self . Spawned {
427
440
let canAdd = _taskGroupAddPendingTask ( group: _group)
428
441
429
442
guard canAdd else {
430
443
// the group is cancelled and is not accepting any new work
431
- return false
444
+ return Spawned ( handle : nil )
432
445
}
433
446
434
447
// Set up the job flags for a new task.
@@ -449,7 +462,22 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
449
462
// Enqueue the resulting job.
450
463
_enqueueJobGlobal ( Builtin . convertTaskToJob ( childTask) )
451
464
452
- return true
465
+ return Spawned ( handle: Task . Handle ( childTask) )
466
+ }
467
+
468
+ public struct Spawned : Sendable {
469
+ /// Returns `true` if the task was successfully spawned in the task group,
470
+ /// `false` otherwise which means that the group was already cancelled and
471
+ /// refused to accept spawn a new child task.
472
+ public var successfully : Bool { handle != nil }
473
+
474
+ /// Task handle for the spawned task group child task,
475
+ /// or `nil` if it was not spawned successfully.
476
+ public let handle : Task . Handle < ChildTaskResult , Error > ?
477
+
478
+ init ( handle: Task . Handle < ChildTaskResult , Error > ? ) {
479
+ self . handle = handle
480
+ }
453
481
}
454
482
455
483
/// Wait for the a child task that was added to the group to complete,
@@ -493,8 +521,8 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
493
521
/// Order of values returned by next() is *completion order*, and not
494
522
/// submission order. I.e. if tasks are added to the group one after another:
495
523
///
496
- /// await group.spawn { 1 }
497
- /// await group.spawn { 2 }
524
+ /// group.spawn { 1 }
525
+ /// group.spawn { 2 }
498
526
///
499
527
/// print(await group.next())
500
528
/// /// Prints "1" OR "2"
@@ -519,6 +547,29 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
519
547
return try await _taskGroupWaitNext ( group: _group)
520
548
}
521
549
550
+ /// - SeeAlso: `next()`
551
+ public mutating func nextResult( ) async throws -> Result < ChildTaskResult , Failure > ? {
552
+ #if NDEBUG
553
+ let callingTask = Builtin . getCurrentAsyncTask ( ) // can't inline into the assert sadly
554
+ assert ( unsafeBitCast ( callingTask, to: size_t. self) ==
555
+ unsafeBitCast ( _task, to: size_t. self) ,
556
+ """
557
+ group.next() invoked from task other than the task which created the group! \
558
+ This means the group must have illegally escaped the withTaskGroup{} scope.
559
+ """ )
560
+ #endif
561
+
562
+ do {
563
+ guard let success: ChildTaskResult = try await _taskGroupWaitNext ( group: _group) else {
564
+ return nil
565
+ }
566
+
567
+ return . success( success)
568
+ } catch {
569
+ return . failure( error as! Failure ) // as!-safe, because we are only allowed to throw Failure (Error)
570
+ }
571
+ }
572
+
522
573
/// Query whether the group has any remaining tasks.
523
574
///
524
575
/// Task groups are always empty upon entry to the `withTaskGroup` body, and
@@ -650,6 +701,7 @@ extension ThrowingTaskGroup: AsyncSequence {
650
701
651
702
/// - SeeAlso: `ThrowingTaskGroup.next()` for a detailed discussion its semantics.
652
703
public mutating func next( ) async throws -> Element ? {
704
+ guard !finished else { return nil }
653
705
do {
654
706
guard let element = try await group. next ( ) else {
655
707
finished = true
0 commit comments