[Concurrency] Asynchronous functions

My colleagues at Google (most notably @gribozavr, @saeta, and @pschuh) and I came up with these questions:


  • Has any thought been given to an async analogue for rethrows?

Proposed solution: async/await

  • “Because asynchronous functions must be able to abandon their thread, and synchronous functions don’t know how to abandon a thread”—My understanding is that the real reason synchronous functions can’t abandon threads is safety: they’ve been written with the expectation of non-reentrancy on blocking calls (and exclusive access to thread-local variables). This paragraph in general is hard to understand because it leads the reader down several obviously impossible paths and is vague. I think we can just say that synchronous functions have been written with the expectation of non-reentrancy on blocking calls, and calling an async function can cause it to be re-entered upon await. If there’s something more we are trying to say here, maybe clarify it?
  • “When control returns to an asynchronous function, it picks up exactly where it was. That doesn’t necessarily mean that it’ll be running on the exact same thread it was before, …. However, many asynchronous functions are … associated with specific actors…. Swift does guarantee that such functions will in fact return to their actor to finish executing.”— It took making all of the elisions for me to come up with a theory of what this means; the “however” was confusing because actors aren’t necessarily associated with threads. Now I think what you’re saying is that there’s no (direct) way to associate an async function call with a given thread, but there is a way to associate it with a given actor (and then associating it with a global actor indirectly associates it with a thread). Is that right?
  • “model those threads as actors in Swift”—it’s not clear what that means. Can you show an example?

Suspension Points

  • “Execution context” is not defined anywhere in these proposals, but it’s used all over the place. It's probably a commonly-used term in general but I can't find an accepted definition that isn't tied to a specific language (mostly JavaScript), and the definitions for some languages (e.g. Scala) seem incompatible with what’s being said about it in the proposals. Can we please have a definition?

  • “Note that suspension points are also called out explicitly in code using explicit callbacks: the suspension happens between the point where the outer function returns and the callback starts running.”—Is “code using explicit callbacks” referring to, e.g., libdispatch uses in existing code? If so, I don't think the point where the outer function returns is relevant. If not, maybe this needs some qualification, like “async code using explicit callbacks” or something. In general, an example would help.

  • “Asynchronous programs that need to do intense computation should generally run it in a separate context.”—what does “context” mean in this, um, context?

  • “When that’s not feasible, there will be library facilities to artificially suspend and allow other operations to be interleaved.”—facilities, plural? Isn't a single empty async function enough to do that?

  • “This design currently provides no way to prevent the current context from interleaving code while an asynchronous function is waiting for an operation in a different context.”—The meaning of “context” is unclear again. Is this “execution context” or a different “context?”

    I think this sentence makes sense if we interpret "context" as "call stack." If that interpretation is correct maybe we should just use “call stack” everywhere.

Asynchronous calls

  • “If the callee’s executor is different from the caller’s executor”—it took me a while to puzzle through this, thinking, “why would we want to forbid the suspension from occurring if the executors match, since it is semantically equivalent?” before I realized this wasn’t an “If and only if.” Still, it might simplify the model if we just say there’s a suspension at each async call.
  • “If the callee’s executor is different from the caller’s executor, a suspension occurs”—Presumably in the caller's executor? It would be clearer if we always said which executor was suspending.
  • “During the return, if the callee’s executor is different from the caller’s executor, a suspension occurs”—Presumably in the callee’s executor? It’s not clear why we need to say that, since the async function executed in the callee is finished and its bit of call stack goes away, there doesn’t seem to be anything to suspend. I don’t think it makes any semantic difference, so perhaps another opportunity to simplify the model?
  • “From the caller's perspective, async calls behave similarly to synchronous calls, except that they may execute on a different executor, requiring the task to be briefly suspended.”—I think this glosses over an important point that must factor into the caller’s perspective: that during suspension other partial tasks may proceed on the same executor. That means data to which the callee has no access—such as the properties of the caller’s actor—may appear to mutate underneath the caller during the call.
  • Could you provide more low-level details about the expected implementation? Where is the overhead? When is memory allocated? Does one partial task object in practice correspond to one function invocation (and reused over multiple suspensions), or one continuation (not reused across suspensions)? Are partial tasks always allocated on the heap? Where do reference counting operations on partial tasks happen? Are they contended?

Detailed Design — Asynchronous Functions

  • “it is the ‘inner’ function type that is async, consistent with the usual rules for such references”—what does this mean? Could you show an example?
  • The handling of overloading is pretty slick!

Detailed Design — Autoclosures

  • “At first glance, the await expression implies to the programmer that there is a suspension point prior to the call to computeArgumentLater(_:)”—this is just the usual problem of autoclosures, is it not?
  • “means that closure would be inferred to have async function type”—That conclusion isn’t obvious. Autoclosures mislead about execution order and whether something will be executed; that’s just how it is. The inference of whether something is async needn’t be done on a purely syntactic basis. The fact the await is in an autoclosure could change the inference.
  • “An equivalent rewriting of the call should be…”—Yeah, again that assumes the usual correlation between syntax and semantics holds, but it doesn’t in the case of autoclosures. It’s not obvious that this is how it has to be.
4 Likes