Modern Concurrency: Beyond the Basics, Episode 5: Using a Buffered AsyncStream | Kodeco

Challenge: Rewrite countdown(to:) using the buffered push-based version of AsyncStream and Timer.scheduledTimer.


This is a companion discussion topic for the original entry at https://www.kodeco.com/32059632-modern-concurrency-beyond-the-basics/lessons/5
1 Like

Xcode shows some warnings for the solution provided.
Reference to captured var 'countdown' in concurrently-executing code; this is an error in Swift 6

Can you please provide an updated version of the block of code without the warning.

afaik there isn’t any fix yet. There’s a note on this episode

Use Xcode 13 for this exercise. Xcode 14 beta uses Swift 6, which doesn’t allow using countdown in the scheduledTRimer closure.

Earlier this year, Marin Todorov / @icanzilb updated the book on which this course is based, and this example is no longer in it.

3 Likes

Hi, the final code neither sends message to chat nor starts timer. I checked with final project as well, no luck. Please assist.

Hi Eytan, please read the posts from April 2023, above yours. This was a known issue and hasn’t been fixed in the book, so there’s no workaround for it.

Hi Eytan and Audrey,

Timer needs a runloop on the thread where it is scheduled, otherwise it will never fire. We can force it to the main thread which has a run loop by wrapping the code inside the AsyncStream trailing closure in a Main-Actor bound Task:

    ...
    let counter = AsyncStream<String> { continuation in
      Task { @MainActor in
        // your challenge code goes here
      }
    }
...

However, we still get the warnings about Swift 6 language mode, which makes me wonder if using a Timer is a good idea. I tried wrapping countdown in an Actor. This way we need to await when accessing it and the warnings go away. But we need to create yet another Task inside the timer closure. At this point we get another Swift 6 warning when we invalidate the timer, because Timer is not Sendable

If we avoid using Timer, we can clean up those warnings quite nicely:

  func countdown(to message: String) async throws {
    guard !message.isEmpty else { return }
    
    let counter = AsyncStream { continuation in
      Task {
        for tick in (0...3).reversed() {
          try await Task.sleep(for: .seconds(1))
          try Task.checkCancellation()
          
          switch tick {
          case (1...):
            continuation.yield("\(tick)...")
          default:
            continuation.yield("🎉 " + message)
            continuation.finish()
          }
        }
      }
    }
    
    for await countdownMessage in counter {
      try await say(countdownMessage)
    }
  }
1 Like