<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class=""><br class=""><div class="">
-Pierre

</div>
<div><br class=""><blockquote type="cite" class=""><div class="">On Sep 2, 2017, at 7:51 PM, Brent Royal-Gordon via swift-evolution &lt;<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><div class=""><blockquote type="cite" class="">On Sep 2, 2017, at 2:56 AM, Pierre Habouzit &lt;<a href="mailto:phabouzit@apple.com" class="">phabouzit@apple.com</a>&gt; wrote:<br class=""><br class=""><blockquote type="cite" class="">`onResume` hooks seem like a really good way to, essentially, allow arbitrary concurrency primitives to be passed into `async` functions. My main question is, if we have it, why do we need coroutine contexts? It seems to me that anything the coroutine might do with the context could either be passed into its parameters, or encapsulated in its `onResume` hook.<br class=""></blockquote><br class="">No it's not the same. Arbitrary code is this: arbitrary code and data.<br class=""><br class="">Please read the few mails I sent recently about this, but to recap here quickly:<br class=""><br class="">It is needed for the runtime (in a broad sense, from language to the operating system) to be able to introspect these:<br class="">- priority attributes<br class="">- dependencies<br class="">- execution contexts (thread/queue/runloop/...)<br class="">- ownership<br class=""><br class="">Without this info, the scheduling of these coroutines will essentially be random, subject to priority inversions and other similar issues.<br class=""></blockquote><br class="">I will freely admit that I don't understand all of these details, so in lieu of rebutting this, I will simply state what I'm saying more explicitly and ask you to explain why I'm wrong in smaller words. :^)<br class=""><br class="">Basically, what I'm saying is: Why do the context details need to be available *within the async function*, rather than being available only to the resume hook?<br class=""><br class="">For a GCD example, suppose the normal, C-based `dispatch_async` function is exposed to Swift as `__dispatch_async`, and `beginAsync` has a signature like this:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// I don't think `rethrows` works this way, but pretend it did.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>//<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>/// Starts running an asynchronous function which is started and restarted by `resumeHook`.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>/// <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>/// - Parameter body: The async function to run.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>/// - Parameter resumeHook: A function called once for each time `body` starts or resumes running. <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>/// <span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>It is passed a `continuation` function representing the next synchronous chunk of <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>///<span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>`body`, which it should run (or schedule to run). If the `continuation` throws or returns <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>///<span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>a non-`nil` value, the function has terminated, and the result should be handled <br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>///<span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>appropriately. If the `continuation` returns `nil`, then it has not finished executing.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func beginAsync&lt;Return&gt;(do body: () async throws -&gt; Return, startingWith resumeHook: @escaping (_ continuation: @escaping () rethrows -&gt; Return?) -&gt; Void) { … }<br class=""><br class="">You can then write async-function-handling versions of `async` like:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>extension DispatchQueue {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs a nullary, non-throwing, Void async function, and can be called from non-async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async -&gt; Void) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>beginAsync(do: body, startingWith: { continuation in<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let workItem = DispatchWorkItem(qos: qos, flags: flags) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>_ = continuation()<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>__dispatch_async(self, workItem)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>})<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs any (nullary) async function, and can be called from async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async&lt;Return&gt;(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async throws -&gt; Return) async rethrows -&gt; Return {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return try await suspendAsync { successHandler, failureHandler in<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>beginAsync(do: body, startingWith: { continuation in<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let workItem = DispatchWorkItem(qos: qos, flags: flags) &nbsp;{<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>do {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>if let returnValue = try continuation() {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>successHandler(returnValue)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>catch {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>failureHandler(returnValue)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>__dispatch_async(self, workItem)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>})<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><br class="">Information like the QoS is encapsulated by the closure, so that each time it enqueues another chunk of work, it attaches that information to it. Is that good enough? Or do you need more?<br class=""></div></div></blockquote><div><br class=""></div><div><br class=""></div>I think it's good enough.</div><div><br class=""><blockquote type="cite" class=""><div class=""><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>* * *<br class=""><br class="">I *think* you might be saying that, when GCD wants to run a given async block,</div></div></blockquote><div><br class=""></div><div>Not only when it runs it, at the time it's enqueued, it needs to introspect it then, to make the context it's enqueued onto inherit the right properties.</div><div>Because we don't want (nor can) look into the enqueued work onto a queue, so we need to coalesce the info at the queue level. And for real the only thing GCD really cares about here is the queue that is at the bottom of the queue hierarchy graph, we do coalesce these info down until we find the bottom queue, and for the sake of scheduling, all enqueued closures are collapsed to that bottom queue which is the owner of all the work recursively enqueued onto it.</div><div><br class=""></div><div>What I'd like is for the closure to have some amount of space that the runtime can mutate so that we can explain to the object in which state it is. This requires for these closures to be a different object than regular lean closures. The latter are shared and can be called several times, async blocks should be called at most once and never reused. IOW GCD would want to augment and maintain this context.</div><div><br class=""></div><div>DispatchWorkItem today tries to be that but without support from the langage can't, so we do this annotation (where you were enqueued) the once, which allows for DispatchWorkItem.wait() (or dispatch_block_wait() in C) to work *once* exactly. This is done with atomics because we have no exclusion guarantee.</div><div><br class=""></div><div>But yeah, you're right that basically we (GCD) would like for coroutines in the async/await world to have at least the annotations DispatchWorkItem has today:</div><div><br class=""></div><div><br class=""></div></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div><div><a href="https://github.com/apple/swift-corelibs-libdispatch/blob/0e35ed9cf9c6de27f4ba8ba606a78d1da551602b/src/block.cpp#L50" class="">https://github.com/apple/swift-corelibs-libdispatch/blob/0e35ed9cf9c6de27f4ba8ba606a78d1da551602b/src/block.cpp#L50</a></div></div></blockquote><div class=""><br class=""></div>which really is this:<div class=""><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div class=""><a href="https://github.com/apple/swift-corelibs-libdispatch/blob/666df60ce98b9f38814b544f9ff1ca74b7bbf70d/src/queue_internal.h#L986-L994" class="">https://github.com/apple/swift-corelibs-libdispatch/blob/666df60ce98b9f38814b544f9ff1ca74b7bbf70d/src/queue_internal.h#L986-L994</a></div></blockquote><div class=""><br class=""></div>And for real most of these fields don't need to be here if we have the guarantee the coroutine is single-use (or really one-use-at-a-time-then-reset-state, this is fine too).<div class=""><br class=""></div><div class="">We don't need significantly *more* for coroutines than that from the GCD perspective.<br class=""><div class=""><div><div><br class=""></div><div><br class=""></div><br class=""><blockquote type="cite" class=""><div class=""><div class=""> it wants to be able to look ahead to where the `successHandler` will want to run so it can schedule the first block on a thread that will be able to immediately run the `successHandler`. But if so, that still only requires `suspendAsync` to extract the context and pass it to its parameter—it doesn't require arbitrary code running when the function is *not* suspended to access the context.<br class=""><br class="">You could perhaps imagine the standard library providing these declarations:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>protocol AsyncContext {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func resumeAsync(_ resumePoint: @escaping () -&gt; Void)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>struct AsyncContinuation&lt;Returning, Throwing: Error&gt; {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// These are notionally packaged up here, although it might actually be implemented differently.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>private let successHandler: (Returning) -&gt; Void<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>private let failureHandler: (Throwing) -&gt; Void<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func resumeAsync(in context: AsyncContext, returning value: Returning) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>context.resumeAsync { successHandler(value) }<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func resumeAsync(in context: AsyncContext, throwing error: Throwing) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>context.resumeAsync { failureHandler(error) }<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func beginAsync(in context: AsyncContext, do body: () async -&gt; Void) { … }<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func suspendAsync&lt;Returning, Throwing&gt;(_ handler: (AsyncContext, AsyncContinuation&lt;Returning, Throwing&gt;) -&gt; Void) async throws&lt;Throwing&gt; -&gt; Returning { … }<br class=""><br class="">Then GCD could do something like:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>extension DispatchQueue {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>struct Context: AsyncContext {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let queue: DispatchQueue<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let qos: DispatchQoS<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let flags: DispatchFlags<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let nextContext: Context?<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func resumeAsync(_ resumePoint: @escaping () -&gt; Void) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let workItem = DispatchWorkItem(qos: qos, flags: flags, block: resumePoint)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>__dispatch_async_with_next_queue(queue, workItem, nextContext?.queue)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs a nullary, non-throwing, Void async function, and can be called from non-async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async -&gt; Void) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>beginAsync(in: Context(queue: self, qos: qos, flags: flags, nextContext: nil), do: body)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs any (nullary) async function, and can be called from async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async&lt;Return&gt;(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async throws -&gt; Return) async rethrows -&gt; Return {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return try await suspendAsync { context, continuation in<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let newContext = Context(queue: self, qos: qos, flags: flags, nextContext: context as? DispatchQueue.Context)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>beginAsync(in: newContext) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>do {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>continuation.resumeAsync(in: context, returning: try await body())<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>catch {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>continuation.resumeAsync(in: context, throwing: error)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><br class="">This allows GCD to look arbitrarily deep into the future, but the context can only be inspected at suspension points; it's otherwise encapsulated. The context is also now in control of execution, rather than being some passive data that may or may not be present and may or may not have any particular meaning.<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>* * *<br class=""><br class="">Actually, looking at this, it seems to me that `beginAsync(in:do:)` internally just creates a continuation for the beginning of an async function and resumes it. With a small language feature addition, we can have this in the standard library:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>protocol AsyncContext {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// …as before…<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>struct AsyncContinuation&lt;Returning, Throwing: Error&gt; {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// …as before…<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>extension AsyncContinuation where Throwing == Never {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>init(starting function: (#splat(Returning)) async -&gt; Void) { … }<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func suspendAsync&lt;Returning, Throwing&gt;(_ handler: (AsyncContext, AsyncContinuation&lt;Returning, Throwing&gt;) -&gt; Void) async throws&lt;Throwing&gt; -&gt; Returning { … }<span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><br class="">And this in GCD:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>extension DispatchQueue {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>struct Context: AsyncContext {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let queue: DispatchQueue<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let qos: DispatchQoS<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let flags: DispatchFlags<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let nextContext: Context?<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func resumeAsync(_ resumePoint: @escaping () -&gt; Void) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let workItem = DispatchWorkItem(qos: qos, flags: flags, block: resumePoint)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>__dispatch_async_with_next_queue(queue, workItem, nextContext?.queue)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs a nullary, non-throwing, Void async function, and can be called from non-async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async -&gt; Void) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let context = Context(queue: self, qos: qos, flags: flags, nextContext: nil)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let starter = AsyncContinuation(starting: body)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>starter.resumeAsync(in: context, returning: ())<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs any (nullary) async function, and can be called from async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async&lt;Return&gt;(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async throws -&gt; Return) async rethrows -&gt; Return {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return try await suspendAsync { context, continuation in<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let newContext = Context(queue: self, qos: qos, flags: flags, nextContext: context as? DispatchQueue.Context)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let starter = AsyncContinuation(starting: {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>do {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>continuation.resumeAsync(in: context, returning: try await body())<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>catch {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>continuation.resumeAsync(in: context, throwing: error)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>})<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>starter.resumeAsync(in: newContext, returning: ())<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><br class="">We could even encapsulate the second version's chaining logic in `AsyncContinuation`:<br class=""><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>extension AsyncContinuation where Throwing == Never {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>init&lt;StarterReturning, StarterThrowing&gt;(starting starter: (#splat(Returning)) async throws&lt;StarterThrowing&gt; -&gt; StarterReturning, returningTo continuation: AsyncContinuation&lt;StarterReturning, StarterThrowing&gt;, in returningContext: AsyncContext) {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>self.init(starting: {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>do {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>continuation.resumeAsync(in: returningContext, returning: try await starter())<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>catch {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>continuation.resumeAsync(in: returningContext, throwing: error)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>})<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>extension DispatchQueue {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// …as before…<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>// This version runs any (nullary) async function, and can be called from async code.<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>func async&lt;Return&gt;(qos: DispatchQoS = .default, flags: DispatchWorkItemFlags = [], execute body: () async throws -&gt; Return) async rethrows -&gt; Return {<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>return try await suspendAsync { context, continuation in<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let newContext = Context(queue: self, qos: qos, flags: flags, nextContext: context as? DispatchQueue.Context)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>let starter = AsyncContinuation(starting: body, returningTo: continuation, in: context)<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>starter.resumeAsync(in: newContext, returning: ())<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}<br class=""><br class="">Make `suspendAsync` a class method on `AsyncContinuation` and we've pretty much walled off all these low-level guts in a single type!<br class=""><br class="">(P.S. Should the `AsyncContext` be a public property of the continuation? Maybe—that would make it harder to accidentally run continuations in the wrong context.)<br class=""><br class="">-- <br class="">Brent Royal-Gordon<br class="">Architechies<br class=""><br class="">_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-evolution<br class=""></div></div></blockquote></div><br class=""></div></div></body></html>