“Unlike other continuations in Swift, AsyncStream.Continuation supports escaping.”

the documentation for AsyncStream<T>.Continuation has a strange warning that reads:

Unlike other continuations in Swift, AsyncStream.Continuation supports escaping.

aren’t all continuations inherently escaping?

I'm also confused by this. In an earlier thread from June 2021, @John_McCall says this about a CheckedContinuation:

Escaping the continuation is the expected use pattern.

1 Like

I think we might be using slightly different senses of "escaping" in those places.

A task continuation can "escape" the compiler's capacity to statically analyze how it's going to be resumed. In fact, we expect it to, because the only alternative would be to force it to be resumed within the static and synchronous duration of the withContinuation block, which would not be very useful.

Once you've resumed a task continuation, you cannot resume it again, which means its useful lifetime is bounded in time. I can see why someone would think of this as a restriction against a kind of "escape", and I think that's how the AsyncStream documentation means it, because IIUC there isn't an analogous restriction. However, I wouldn't personally call this an "escape" because I think it's better to reserve "escape" for this concept of a statically-enforceable limitation on the use of a value.

5 Likes

Thanks John.

I agree.

I didn't really understand exactly what @John_McCall meant above. I think he is saying that this is an expected use pattern:

// Is this an expected and acceptable use pattern?
    private var continuation: AsyncStream<String>.Continuation?

    lazy var outputStream = AsyncStream<String> { continuation in
        self.continuation = continuation
    }
    
    private func pushValue() {
        continuation?.yield("My String")
    }

Afaict this works and doesn't appear to be broken in any way, but it feels a little strange to me. If this is "not the right way", please let me know :slight_smile:

Also note that, in the example above, the stream is intended to always be "open", and so you never need to finish the continuation.

Edit: Nevermind, read through SE-0314 (Second review): AsyncStream and AsyncThrowingStream - #37 by yuriferretti, where it was confirmed that storing the continuation outside the closure is expected and fine.

Also found @taylorswift's other thread: `AsyncStream` constructor which also returns its Continuation

I agree that it is a rather awkward pattern, I would be in favor of either a constructor that returns the continuation too, or another primitive that is better suited for this scenario :slight_smile:

1 Like

I've just run into this: (a) it is awkward to always grab the continuation from the closure.

But (b) it is even worse, because what if you need to call continuation.yield() before someone has begun iterating? Then the continuation hasn't even been set. (I just posted to ask directly about this and then found this thread.)

Returning the continuation from the constructor wouldn't just be a nicety in this case, it would be essential.

Any further work in this area?

Coming in Swift 5.9: SE-0388: Convenience Async[Throwing]Stream.makeStream methods

1 Like

Awesome. Will solve both my problems.

What is a rough time estimate for when the shipping version of Xcode would have swift 5.9?
I think I'll wait to abandon DispatchSemaphore in favor of async streams until this comes out...

Actually, will the back deployment code make this available any sooner than swift 5.9?

Apple won't say of course, but it's a pretty safe bet that Swift 5.9 will be released in September/October 2023, together with iOS 17 and macOS 14.

1 Like

Actually, I don't understand the solution proposed: in my experiments, the closure which sets continuation in the outside scope doesn't run until the first time someone pulls from the stream.

How does the proposed implementation solve this? (Unless there is more code being added than I see in the proposal...)

Edit: NEVER MIND.

I made a bad mistake: I forgot that static vars are inherently lazy, and in fact the closure is called right away.

Nothing to see, folks...