@@ -321,42 +321,33 @@ by calling the [`Task.yield()`][] method.
321
321
322
322
``` swift
323
323
func generateSlideshow (forGallery gallery : String ) async {
324
- let photos = await listPhotos (inGallery : gallery)
325
- for photo in photos {
326
- // ... render a few seconds of video for this photo ...
327
- Task.yield ()
328
- }
324
+ let photos = await listPhotos (inGallery : gallery)
325
+ for photo in photos {
326
+ // ... render a few seconds of video for this photo ...
327
+ await Task.yield ()
328
+ }
329
329
}
330
330
```
331
331
332
- Because ` generateSlideshow(for:) ` is a synchronous function,
333
- it can't contain any potential suspension points.
334
- However, calling it repeatedly for every photograph in a large gallery
335
- might take a long time.
336
- Explicitly inserting a possible suspension point with ` Task.yield() `
337
- lets Swift balance between making progress on this task,
332
+ Assuming the code that renders video is synchronous,
333
+ it doesn't contain any suspension points.
334
+ However, that work might take a long time.
335
+ Periodically calling ` Task.yield() ` explicitly adds suspension points,
336
+ which let Swift balance between making progress on this work,
338
337
and letting other tasks make progress on other work.
339
- Without the suspension point,
340
- this task would render the entire slideshow without ever yielding its thread.
341
-
342
- <!--
343
- TR: Do we have some general guidance about when you need to insert yield?
344
- SE-0304 says "if all are executing on a shared, limited-concurrency pool".
345
- -->
346
338
347
- The [ ` Task.sleep(until :tolerance:clock:) ` ] [ ] method
339
+ The [ ` Task.sleep(for :tolerance:clock:) ` ] [ ] method
348
340
is useful when writing simple code
349
341
to learn how concurrency works.
350
- This method does nothing,
351
- but waits at least the given number of nanoseconds before it returns.
342
+ This method suspends the current task for at least the given amount of time.
352
343
Here's a version of the ` listPhotos(inGallery:) ` function
353
- that uses ` sleep(until :tolerance:clock:) ` to simulate waiting for a network operation:
344
+ that uses ` sleep(for :tolerance:clock:) ` to simulate waiting for a network operation:
354
345
355
- [ `Task.sleep(until :tolerance:clock:)` ] : https://developer.apple.com/documentation/swift/task/sleep(until :tolerance:clock:)
346
+ [ `Task.sleep(for :tolerance:clock:)` ] : https://developer.apple.com/documentation/swift/task/sleep(for :tolerance:clock:)
356
347
357
348
``` swift
358
349
func listPhotos (inGallery name : String ) async throws -> [String ] {
359
- try await Task.sleep (until : .now + . seconds (2 ), clock : . continuous )
350
+ try await Task.sleep (for : .seconds (2 ))
360
351
return [" IMG001" , " IMG99" , " IMG0404" ]
361
352
}
362
353
```
@@ -367,7 +358,7 @@ func listPhotos(inGallery name: String) async throws -> [String] {
367
358
```swifttest
368
359
>> struct Data {} // Instead of actually importing Foundation
369
360
-> func listPhotos(inGallery name: String) async throws -> [String] {
370
- try await Task.sleep(until : .now + . seconds(2), clock: .continuous )
361
+ try await Task.sleep(for : .seconds(2))
371
362
return ["IMG001", "IMG99", "IMG0404"]
372
363
}
373
364
```
@@ -398,9 +389,9 @@ from nonthrowing code.
398
389
For example:
399
390
400
391
``` swift
401
- func getRainyWeekendPhotos () await -> Result<[String ]> {
392
+ func getRainyWeekendPhotos () async -> Result<[String ]> {
402
393
return Result {
403
- photos = try await listPhotos (inGallery : " A Rainy Weekend" )
394
+ try await listPhotos (inGallery : " A Rainy Weekend" )
404
395
}
405
396
}
406
397
```
@@ -409,8 +400,8 @@ In contrast,
409
400
there's no safe way to wrap asynchronous code
410
401
so you can call it from synchronous code and wait for the result.
411
402
The Swift standard library intentionally omits this unsafe functionality,
412
- and trying to implement it yourself introduces
413
- problems like memory races, threading bugs , and deadlocks.
403
+ and trying to implement it yourself can lead to
404
+ problems like subtle races, threading issues , and deadlocks.
414
405
415
406
<!--
416
407
OUTLINE
@@ -634,7 +625,7 @@ Because of the explicit relationship between tasks and task groups,
634
625
this approach is called * structured concurrency* .
635
626
Although you take on some of the responsibility for correctness,
636
627
the explicit parent-child relationships between tasks
637
- let Swift handle some behaviors like propagating cancellation for you,
628
+ lets Swift handle some behaviors like propagating cancellation for you,
638
629
and lets Swift detect some errors at compile time.
639
630
640
631
[ `TaskGroup` ] : https://developer.apple.com/documentation/swift/taskgroup
@@ -645,17 +636,23 @@ Here's another version of the code to download photos
645
636
that handles any number of photos:
646
637
647
638
``` swift
648
- await withTaskGroup (of : Data.self ) { taskGroup in
639
+ await withTaskGroup (of : Data.self ) { group in
649
640
let photoNames = await listPhotos (inGallery : " Summer Vacation" )
650
641
for name in photoNames {
651
- taskGroup .addTask {
642
+ group .addTask {
652
643
let photo = downloadPhoto (named : name)
653
644
show (photo)
654
645
}
655
646
}
656
647
}
657
648
```
658
649
650
+ <!-- XXX
651
+ The above listing uses TaskGroup<Data> but doesn't return Data,
652
+ and its child tasks also don't return Data.
653
+ I think that's a mistake.
654
+ -->
655
+
659
656
The code above creates a new task group,
660
657
and then creates child tasks of that task group
661
658
to download each photo is the gallery.
@@ -679,14 +676,14 @@ you add code that accumulates its result
679
676
inside the closure you pass to ` withTaskGroup(of:returning:body:) ` .
680
677
681
678
```
682
- let photos = await withTaskGroup(of: Data.self) { taskGroup in
679
+ let photos = await withTaskGroup(of: Data.self) { group in
683
680
let photoNames = await listPhotos(inGallery: "Summer Vacation")
684
681
for name in photoNames {
685
- taskGroup .addTask { await downloadPhoto(named: name) }
682
+ group .addTask { await downloadPhoto(named: name) }
686
683
}
687
684
688
685
var results: [Data] = []
689
- for await photo in taskGroup {
686
+ for await photo in group {
690
687
results.append(photo)
691
688
}
692
689
return results
@@ -726,7 +723,7 @@ There are two ways a task can do this:
726
723
by calling the [ ` Task.checkCancellation() ` ] [ ] method,
727
724
or by reading the [ ` Task.isCancelled ` ] [ ] property.
728
725
Calling ` checkCancellation() ` throws an error if the task is canceled;
729
- a throwing task can let that error propagate out of the task,
726
+ a throwing task can propagate the error out of the task,
730
727
stopping all of the task's work.
731
728
This has the advantage of being simple to implement and understand.
732
729
For more flexibility, use the ` isCancelled ` property,
@@ -737,25 +734,25 @@ like closing network connections and deleting temporary files.
737
734
[ `Task.isCancelled` ] : https://developer.apple.com/documentation/swift/task/3814832-iscancelled
738
735
739
736
```
740
- let photos = await withTaskGroup(of: Data.self) { taskGroup in
737
+ let photos = await withTaskGroup(of: Optional< Data> .self) { group in
741
738
let photoNames = await listPhotos(inGallery: "Summer Vacation")
742
739
for name in photoNames {
743
- taskGroup .addTaskUnlessCancelled {
740
+ group .addTaskUnlessCancelled {
744
741
guard isCancelled == false else { return nil }
745
742
return await downloadPhoto(named: name)
746
743
}
747
744
}
748
745
749
746
var results: [Data] = []
750
- for await photo in taskGroup {
747
+ for await photo in group {
751
748
if let photo { results.append(photo) }
752
749
}
753
750
return results
754
751
}
755
752
```
756
753
757
754
<!-- XXX TR:
758
- Should the listing above add a call to taskGroup .cancelAll()
755
+ Should the listing above add a call to group .cancelAll()
759
756
to show that part of cancellation?
760
757
If so, what's the best practice for where to put that?
761
758
-->
@@ -783,7 +780,7 @@ A full version of this code would include clean-up work
783
780
as part of cancellation,
784
781
like deleting partial downloads and closing network connections.
785
782
786
- An task that isn't part of a task group can handle cancellation
783
+ A task that isn't part of a task group can handle cancellation
787
784
using the [ ` Task.withTaskCancellationHandler(operation:onCancel:) ` ] [ ] method.
788
785
For example:
789
786
@@ -806,11 +803,11 @@ let video = await Task.withTaskCancellationHandler {
806
803
807
804
::
808
805
809
- let handle = spawnDetached {
806
+ let handle = Task.detached {
810
807
await withTaskGroup(of: Bool.self) { group in
811
808
var done = false
812
809
while done {
813
- await group.spawn { Task.isCancelled } // is this child task canceled?
810
+ await group.addTask { Task.isCancelled } // is this child task canceled?
814
811
done = try await group.next() ?? false
815
812
}
816
813
print("done!") // <1>
0 commit comments