Can someone recommend a good, modern tutorial on concurrency?

Can anyone recommend a clear, modern tutorial on concurrency in Swift?

There's a lot of obsolete information out there about Swift, and its concurrency model is a great example. Apparently the original way was to use a return closure and now there's an await keyword...which makes no sense to me, since that's simply turning an async function back into a sync function. I must be missing something.

6 Likes

TSPL (the primary, central documentation for the Swift language) has a section on Concurrency that should serve as a pretty good introduction to the concurrency model in Swift (unless you've already read through this and there's still parts of the model that aren't clicking for you?).

7 Likes

And after main docs, Migration Guide includes also valuable bits of more advanced cases. WWDC videos on the topic also are great (start few years back, and go forward).

2 Likes

Thanks, folks. Yes, these docs are clearer than what I had found.

I think a big chunk of my confusion and frustration is that the language designers chose the word await instead of the more standard yield. 'Await' sounds like you are sitting there waiting for the code to come back to you -- essentially turning your "async" code back into synchronous code and thereby defeating the entire point. 'Yield' is a standard word that's used throughout the industry and has clear intent -- "yield the processor to someone else, I can wait".

None of the tutorials that I had found thus far said anything on the lines of "await actually means yield, not wait" which would have clarified the whole thing. Hence the aforementioned frustration.

Anyway, thanks again for the pointers.

Is the Swift async-await pattern not consistent with other ecosystems for that?

3 Likes

I cannot think of any wide-spread language that uses yield keyword for concurrency, all of them go with async/await pair.

That's kinda true if you think about it in terms of mechanics local to the function: this particular function is actually (a)waits for the result of asynchronous operation (I'm not sure if that's a correct model to think in general, but it has some sense imo).

6 Likes

Worth noting also that there is a Task.yield() which performs the 'give up current executor and perhaps allow something else to run' operation, but the semantics are (unsurprisingly) quite different from await. Rather than 'await' the result of any particular operation, your task will be eligible for rescheduling right away. And as the docs note, if you're the highest-priority task, you'll end up being rescheduled immediately.

4 Likes

FWIW yield is a semi-established keyword proposed for modify accessors, but disregarding that, it reads a voluntary intent to give up a thread or task (as opposed to a required action of awaiting a value). See also Task.yield, which can be used to interrupt a long-running task to return to the scheduler.


Networking being the poster child example of async ops, compare the following two:

let userAvatar = await MyServer.getUserAvatar(userId: "abc")

vs.

let userAvatar = yield MyServer.getUserAvatar(userId: "abc")

await is just a better vocabulary for such kinds of actions.

5 Likes

It's possible that I don't understand the semantics since I don't work in these languages very much, but C#, Ruby, JavaScript, and Python all have a 'yield' keyword and it sure looks like concurrency to me. I suppose it depends on definitions, though.

I will paint that bike shed with my favorite color, dangit! :>

Thank you, everyone, for the great information.

Can say for JS and Python, where it is used for generator functions (streams of values), and have async/await pairs for asynchronous calls (C# I guess as well in this list), with differences in underlying mechanics (like for JS is just a nicer syntax over promises IIRC).

I guess that goes back to the question of definitions. Is a coroutine a type of concurrency? I would say yes, but it sounds like you disagree.

Anyway, not critical. Like I joked above, it's a bike shed[1] and it's entirely reasonable that the expert language designers who created Swift didn't take my preferences into account when choosing their keywords.


[1] In case this is too obscure: the joke is that when you're designing a nuclear power plant (or a programming language) non-experts will shut their mouths. On the other hand, when you're deciding what color to paint the bike shed, every nitwit on the internet has an opinion that they will defend passionately. My wits, of course, are as nitty as one could ask. :>

1 Like

Periodically, I go there looking for new things. :slight_smile:

A lot of critical information is still missing; for example, actor reentrancy.

When calling an asynchronous method, execution suspends until that method returns.

I know what that means. But, can someone who is new to concurrency truly grasp the meaning of that, the suspension of execution, without the knowledge of important concepts such as task, thread, and os process ?

2 Likes

I think it's in general, but that doesn't necessarily mean async/await are bad keywords. Even Python has them. Below are excepted from PEP 492:

async def functions are always coroutines

await, similarly to yield from. It uses the yield from implementation with an extra step of validating its argument.


BTW, while Swift concurrency is based on coroutine (e.g. the cooperative thread pool), my observation is that the Swift community never use the term when discussing concurrency (perhaps because there isn't general coroutine support in Swift yet, I guess).

As far as I understand, this is exactly the case. But since this is not executed on the main thread (you cannot use await in main thread as far as I know), this does not block main thread. It just pauses the current non-main thread until something is processed that is needed for the current thread to continue.

Yield sounds like it is intended for cooperative multitasking, which was the case in classic macOS, before OS X. Modern operating systems use preemptive multitasking, where all threads are running in parallel, and await in one thread does not affect execution of all other threads, including main thread, they continue their execution.

I believe you completely misunderstand how async functions work. They are implemented as coroutines by compiler. Coroutine don't block because they give up the control voluntarily. There is a reason why the new cooperative thread pool was introduced to support concurrency. If your understanding was true, it wouldn't be required.

2 Likes

Good article I recommend.

Swift Async and Await. Asynchronous code | by Bassem Qoulta | Medium.

I wasn't saying for coroutines in general (as was mentioned already, Swift also has coroutines backing concurrency), but only yield keyword that is mostly used in generator functions, while for other asynchronous tasks languages went with async/await. With coroutines as building blocks for concurrency, you still need deeper support (e.g. scheduler) to have full featured asynchronous support, otherwise you'll be limited in capabilites.

That is not what happening at all. You can await on main thread/actor, because it won't block it, but suspend the function that awaits, allowing it to run other work, while awaited task is performed by another thread. The key idea is not to pause threads, but have them running all the time some task.

1 Like

Indeed, I can. Sorry for the incorrect information. And in my experience this blocks the main thread. E.g. this code

func asyncFunc () async {
    Thread.sleep(forTimeInterval: 5)
    print("async func output")
}

print("will call async func")
await asyncFunc()
print("did call async func")

Prints "async func output" and "did call async func" only after 5 seconds.

How is this possible at all - to suspend a function and allow a caller thread do other work? Caller thread will wait for the function to return and not execute any further until function returns.