# SE-0300: Continuations for interfacing async tasks with synchronous code

**URL:** <https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891>\
**Category:** Proposal Reviews\
**Created:** [January 16, 2021, 12:21am UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891 "2021-01-16T00:21:33Z")\
**Posts on this page:** 13\
**Page:** 3

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [January 21, 2021, 8:19pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/41 "2021-01-21T20:19:26Z")

</div>

> [@gwynne](#):
>
> - Are checked continuation failures due to multiple resumes assertions (debug config only), preconditions (debug and release), or "hard" checks (effective when built with `-Ounchecked` )? In short, do "checked" continuations devolve into "unsafe" continuations in release and/or `-Ounchecked` builds? (For the record, given the choice, I would want them to be preconditions - corrupted process state is still corrupt in a release build!)
> - Along the same line, do warnings about checked continuations being lost appear in release builds?

The checks (both traps and warnings) should happen at all optimization levels. I can make this explicit in the proposal.

> [@Jon\_Shier](#):
>
> I don't see anything in the proposal about APIs which provide completion handlers _and_ the queue they run on. Customizable completion queues have been the recommended API design for a while now, it would be a shame if they weren't supported. (If we hadn't gotten rid of `DispatchQueue.current` , that would seem to be an ideal choice here.)

The queue from which `resume` is invoked is irrelevant, since it only transitions the task out of the suspended state; tasks already know what executor they're associated with, and will be scheduled on whatever queue/runloop/thread/other scheduling mechanism their current executor uses. If an API takes a queue argument on which to run its completion handler, it's probably best to pass the queue that the code dispatching the completion handler will run on, if you know it. In cases when we know a task's work can be safely enqueued on a specific dispatch, to allow the queue hops to be minimized, we could perhaps provide some API on `Task` to get that queue for the current task, and have an `unsafeResumeImmediately` variant of API on `*Continuation` that immediately resumes execution of the task on the current thread, relying on the assumption that the code invoking `resume` was already executed by the correct queue. This would be a very sharp knife, though, and using it incorrectly could lead to really subtle problems that'd be tough to diagnose, so I think it'd be good to take a wait-and-see approach to see if queue-hopping becomes a bottleneck in practice. Despite the API recommendations, nearly all of the completion-handler-based APIs in Apple's frameworks do not take a queue argument, and there's not a lot of consistency in how completion handlers are invoked, so very few Apple APIs would be able to take advantage of such an optimization today. And as more code adopts Swift's native tasks and async functions, the Swift compiler and runtime's own optimizations for avoiding expensive queue hops will hopefully reduce overall overhead in time.

> [@Chris\_Lattner3](#):
>
> What is the expected overhead of CheckedContinuation, and is it possible to avoid that overhead without duplicating the API?

As currently implemented, `UnsafeContinuation` is a plain pointer to the task structured to be resumed, whereas `CheckedContinuation` allocates a class instance to hold the task pointer, so compared to `UnsafeContinuation`, it'll have ARC operations applied to it when copied around, and will use atomic operations to "take" the task pointer from the instance the first time it's resumed so that the double-resume protection is thread safe. When we have move-only types, we should be able to make a safe  
`Continuation` type that has to be `resume`-d in order to dispose of it; before then, I don't see an obvious way to avoid the overhead in the language today. However, even with move-only types, since a lot of the  
value of this API is interop with existing C and ObjC APIs, I wonder whether a move-only continuation type would be very practical to use, or just force the use of unsafe escape hatches down a layer.

> [@Lantua](#):
>
> > - Either the resume function must only be called _exactly-once_ on each execution path the operation may take (including any error handling paths), or else
> > - the resume function must be called exactly at the end of the operation function's execution.
> 
> Why are these separate cases? The latter seems to be included in the former.

I can reword the proposal to make this clearer. The distinction that we're trying to make is between `resume` happening after the `with*Continuation` block has finished executing and the task has suspended, and `resume` happening while the `with*Continuation` block is still executing. An example of the latter would be:

```swift
await withUnsafeContinuation { c in
  if condition {
    // Suspend the task and resume it later
    doSomethingAsynchronously(completion: { c.resume(returning: ()) })
  } else {
    // Resume the task immediately
    c.resume(returning: ())

    // According to rule 2, we can't do anything after `resume` if we run
    // it immediately
  }
}

```

I'll amend the text to make this clearer.

> [@Max\_Desiatov](#):
>
> I think there was a question previously in the pitch thread that I don't remember being addressed, asking why `UnsafeContinuation<T>` and `UnsafeThrowingContinuation<T>` (same for the checked variants) are separate types instead of introducing a single `UnsafeContinuation<Result, Failure: Error>` type?
> 
> This separation of types and making the error untyped is inconsistent with `Task.Handle<Success, Failure: Error>` in the structured concurrency pitch, and the already present `Result<Success, Failure: Error>` type.
> 
> I also don't see this addressed in the proposal itself. Is there a specific reason for this?

The error propagation from `with*Continuation` is either-or; if you specified a more specific Error type, it would have no type system impact on what the caller sees when `withUnsafeThrowingContinuation` `throws`. `Task.Handle`'s error parameterization seems a bit suspect to me for this reason too; in practice it can only ever be `Never` or `Error`.

> [@Chris\_Lattner3](#):
>
> Why does this leak a task? Is this a logic error like a leak or is this a memory safety violation? If this is a memory safety problem, then this should probably _also_ be an unsafe API, even if it is slightly safer. If it is memory safe but "just" a logic error, then I don't think the rationale above is super strong. We have lots of logic errors that happen from API misuses, and we don't dance around that. For example, "Sequence" in a generic context is an unchecked single iteration sort of beast and is often misused, we don't encode fear into the name.

It's a leak, and potentially a deadlock if other work relies on the abandoned task finishing, but it's not a memory safety violation. If the task is never resumed, it'll sit around forever holding onto whatever resources it was using when it suspended. My bigger concern about making leaking a task trap is the lack of determinism in where that trap happens; one could miss a trap because naive -Onone ARC put off destroying the checked continuation wrapper until the process ends for other reasons, and then end up shipping a -O build that cuts the lifetime shorter and exposes the trap.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [January 21, 2021, 8:45pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/42 "2021-01-21T20:45:07Z")

</div>

> [@Joe\_Groff](#):
>
> The queue from which `resume` is invoked is irrelevant, since it only transitions the task out of the suspended state; tasks already know what executor they're associated with, and will be scheduled on whatever queue/runloop/thread/other scheduling mechanism their current executor uses. If an API takes a queue argument on which to run its completion handler, it's probably best to pass the queue that the code dispatching the completion handler will run on, if you know it. In cases when we know a task's work can be safely enqueued on a specific dispatch, to allow the queue hops to be minimized, we could perhaps provide some API on `Task` to get that queue for the current task, and have an `unsafeResumeImmediately` variant of API on `*Continuation` that immediately resumes execution of the task on the current thread, relying on the assumption that the code invoking `resume` was already executed by the correct queue. This would be a very sharp knife, though, and using it incorrectly could lead to really subtle problems that'd be tough to diagnose, so I think it'd be good to take a wait-and-see approach to see if queue-hopping becomes a bottleneck in practice. Despite the API recommendations, nearly all of the completion-handler-based APIs in Apple's frameworks do not take a queue argument, and there's not a lot of consistency in how completion handlers are invoked, so very few Apple APIs would be able to take advantage of such an optimization today. And as more code adopts Swift's native tasks and async functions, the Swift compiler and runtime's own optimizations for avoiding expensive queue hops will hopefully reduce overall overhead in time.

Based on other replies up thread, especially [Ben\_Cohen's](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/22), the queue on which `resume` is invoked is anything but irrelevant, as it determines the queue on which the resumed code is executed. Or is the phrase "absent anything else like actors being involved" doing the hard work there, because there's really no way to execute `async` code without being in a context that would guarantee execution resumes on the original queue? So is this real behavior (which would be an issue with this proposal), or is it a behavior that won't exist in the fully concurrency story? That is, to use the current snapshot as an example, where does the code execution resume in the example below?

```swift
runAsyncAndBlock {
    // On runAsyncAndBlock's backing queue.
   let string = await makeNetworkCall() // Wrapper which resumes a continuation on URLSession's arbitrary completion queue.
    print(string) // What queue is this on? runAsyncAndBlock's backing queue, or URLSession's completion queue?
}

```

And despite Apple's frameworks not usually following the practice, largely due to age or the friction of passing `DispatchQueue`'s in Obj-C, it has certainly been the recommendation by Apple engineers on social media and in WWDC labs, as well as the community at large, to pass queues for completion handlers. So when people bring up best practice, we don't just mean the practices visible in Apple's SDKs but those the Swift community have been using over the last several years.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [January 21, 2021, 8:48pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/43 "2021-01-21T20:48:54Z")

</div>

> [@Jon\_Shier](#):
>
> Based on other replies up thread, especially [Ben\_Cohen's](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/22), the queue on which `resume` is invoked is anything but irrelevant, as it determines the queue on which the resumed code is executed.

`resume` does not immediately resume the task, it only makes it ready to be resumed again. Its executor/actor will resume the task in a context appropriate for its current state. I'll make this clearer in the proposal text. In your example, `runAsyncAndBlock` creates a task that takes over the main thread; after `makeNetworkCall` suspends the task, it ought to resume on the main thread once its continuation is resumed.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [January 21, 2021, 8:52pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/44 "2021-01-21T20:52:45Z")

</div>

Ah yes, that does clear it up, thanks. With that being the case, queue hopping becomes an optimization problem I'm happy to leave for later.

---

<div class="post-metadata">

**Author:** ![michelf](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/michelf/32/1180_2.png) [@michelf](https://forums.swift.org/u/michelf)\
**Post date:** [January 21, 2021, 10:37pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/45 "2021-01-21T22:37:06Z")

</div>

> [@Joe\_Groff](#):
>
> ```swift
> await withUnsafeContinuation { c in
> if condition {
> // Suspend the task and resume it later
> doSomethingAsynchronously(completion: { c.resume(returning: ()) })
> } else {
> // Resume the task immediately
> c.resume(returning: ())
> 
> // According to rule 2, we can't do anything after `resume` if we run
> // it immediately
> }
> }
> 
> ```

The second comment in this example really bugs me. Does "can't do anything" imply no `defer` blocks? No ARC-triggered `deinit`? What happens if you do something like `print("hi")` after the `c.resume` line?

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [January 22, 2021, 2:45am UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/46 "2021-01-22T02:45:56Z")

</div>

> [@michelf](#):
>
> The second comment in this example really bugs me. Does "can't do anything" imply no `defer` blocks? No ARC-triggered `deinit` ? What happens if you do something like `print("hi")` after the `c.resume` line?

After asking around, it sounds like this second restriction might not be needed. Certainly the current implementation shouldn't depend on it. I'll remove this.

---

<div class="post-metadata">

**Author:** ![Lantua](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lantua/32/8868_2.png) [@Lantua](https://forums.swift.org/u/Lantua)\
**Post date:** [January 22, 2021, 3:34am UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/47 "2021-01-22T03:34:17Z")

</div>

The first invariance:

> - Either the resume function must only be called _exactly-once_ after the operation function passed into `withUnsafeContinuation` has finished executing, on every execution path through the program,

sounds tricky to comply. Even if I do:

```swift
withUnsafeContinuation { operation in
  DispatchQueue.global().async {
    operation.resume(...)
  }

  // Some clean up
}

```

There's no guarantee that `DispatchQueue` will be executed _after_ `operation` has ended. Even if we don't have anything after `DispatchQueue.async`, it may still be executed while we're unrolling the stack, however unlikely it is.

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [January 22, 2021, 5:18pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/48 "2021-01-22T17:18:11Z")

</div>

I don't think this restriction is really necessary, and I asked the other authors of the proposal and we can't recall why that restriction was there. I'll remove it.

---

<div class="post-metadata">

**Author:** ![Lantua](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lantua/32/8868_2.png) [@Lantua](https://forums.swift.org/u/Lantua)\
**Post date:** [January 22, 2021, 5:29pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/49 "2021-01-22T17:29:52Z")

</div>

Does this imply that `continuation` is not run on any executor, or at the very least, different one from the caller of `withContinuation`?

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [January 22, 2021, 5:31pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/50 "2021-01-22T17:31:16Z")

</div>

`resume` only transitions the task back to a schedulable state, and then returns control back to its caller. The task will resume execution when its executor from the time it was suspended reschedules it.

---

<div class="post-metadata">

**Author:** ![Lantua](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lantua/32/8868_2.png) [@Lantua](https://forums.swift.org/u/Lantua)\
**Post date:** [January 24, 2021, 7:03pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/51 "2021-01-24T19:03:12Z")

</div>

So we could say that `resume` enqueues the next partial task and immediately returns. I'm all for simple rules, so removing the restriction is a welcomed addition.

What about `withUnsafeContinuation ` itself? Does it enqueue `operation` on a different (unspecified) executor somewhere and immediately suspend the task, or does it execute the `operation` and suspend the task once `operation` completes? I think it's part of the semantic that should be documented. At the very least, we can decide whether to recommend _not to perform long-running tasks inside `continuation`_ (though it should be done using `Task.runDetached` anyway).

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [January 24, 2021, 8:50pm UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/52 "2021-01-24T20:50:43Z")

</div>

The `withUnsafeContinuation` operation runs immediately in the about-to-be-suspended task's current context.

---

<div class="post-metadata">

**Author:** ![ben-cohen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/ben-cohen/32/296_2.png) [@ben-cohen](https://forums.swift.org/u/ben-cohen)\
**Post date:** [February 2, 2021, 4:50am UTC](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891/53 "2021-02-02T04:50:42Z")

</div>

# Review Conclusion

Thanks to everyone who participated in this review. Given a number of clarifications of behavior emerged during discussion with the proposal authors, the core team has decided to run a [second review](https://forums.swift.org/t/se-0300-second-review-continuations-for-interfacing-async-tasks-with-synchronous-code/) of the clarified proposal before a final review decision.

[Previous page](https://forums.swift.org/t/se-0300-continuations-for-interfacing-async-tasks-with-synchronous-code/43891.md?page=2)
