I’d like to clarify my understanding. Consider this claim:
As implemented in Swift, async/await on its own would be useless without the ability to create tasks (structured, unstructured, or detached), i.e. to introduce concurrency. So long as there is only one task*, while a function can suspend, there's really no point because there would be no other work to execute while awaiting.
Is this correct?
* I'm assuming every program starts with a default task created by the runtime.
I think you're talking about raw coroutines, no? If so, they're still useful on their own, as seen in a few languages (Kotlin I think, maybe the newest C++?), and async / await can just be sugar over them. In JavaScript async / await is just sugar over their promise system, which is concurrent but not parallel. So I don't think it's quite correct to say it'd be useless, at least in the general sense.
Thanks Jon. I know async/await is implemented differently in other languages and coroutines are useful. AFAIK Swift even uses coroutines for one other feature (_modify), but I mean my question specifically in the context of Swift’s concurrency model.
Maybe I can rephrase the claim like this: a Swift program that uses async/await but never creates any task is meaningless because it would behave excatly like a corresponding synchronous program (that uses blocking calls).
Edit: I’m not sure it makes sense to even ask the question like this. Sorry.
Eh, I think your question has essentially defined away any possible affirmative response. You're essentially asking if a language with the keywords async and await, but with no notion of concurrency, does anything concurrently. I mean, the answer is no, just the same as a language with a for construct that isn't actually backed by a looping mechanism doesn't actually have loops.
If you're asking about the "why" of async / await, I think the original proposal has some insight here, as well as the reasoning of the other languages that implement it. But fundamentally, I think async / await assumes (at least what looks like) the ability to suspend execution and continue it later.
I think, strictly speaking, the answer to your question is no, it could still be useful. After all, even without tasks, your system is running multiple processes. Consider a 2 core system with 10 well-behaved Swift processes (i.e. they hit suspension points reasonably often) all of which are using Swift concurrency only. The system could potentially only have 2 threads in total, which it gives to the processes which are executing (this works because async/await allows a task to give up the thread it is executing on when it suspends). This is ~identical to the concurrency between tasks but one level higher with processes.
i believe the answer to the original question is "yes", as you can only call async functions from other async functions and given that the top level functions are sync (e.g. main or viewDidLoad or body) somewhere you'd need to break that viral chain and have a task (e.g. made with the async { ... } construct) just to avoid the compilation error. this is different compared to promise based async/await implementations.
I don't know the Swift specifics but even if you happen to have just one thread, the implementation often rely on task queues, so you are still able to post task's to that thread pool queue, so is not useless, you would still get async behavior except if one of the tasks you have posted to that thread pool (with one single main thread) is doing some work that is preventing the next tasks from running. In that case the next tasks you post wont execute.