SE-0317: Async Let

The naming here unifies all of the pieces of structured concurrency around async: async functions, group.async and async let provide normal control flow for asynchronous and concurrent code (try, throw, return, if, etc. all do the same things as in synchronous code), obey normal lexical scoping rules (e.g., child tasks never persist beyond the scope in which they are introduced), need to be await'd to get results, inherit important context like priority and task local values, and rigorously . async is the tool you should reach for for structured concurrency, but that's what gives you a programming model that's easiest to reason about and is the most optimizable.

Unstructured concurrency involves working with task instances, so new unstructured tasks are created with a completely different syntax that emphasizes task creation: Task { ... }. With unstructured concurrency, you get none of the affordances to help manage concurrency well. We absolutely need to have support for unstructured concurrency, but it's not the first thing one should reach for.

The naming emphasizes that the core distinction is Structured vs. Unstructured. Renaming async let or group.async means that we end up with more than these two categories, or multiple bits of divergent terminology within the same category (e.g., with async functions and group.spawn and Task.detached, why would I have any reason to think that spawn is part of structured concurrency?). So while it might make some things more explicit (yes, group.spawn spawns a new task of some sort!), it loses cohesion within the more important groups.

I also think it's fairly hard to justify that position that group.async { ... } is unclear about the fact that it's initiating new work running asynchronously from the call site, given that we've been using DispatchQueue.async to do exactly this for many years.

Doug

10 Likes