Swift async/await is such a foot gun that induces deadlock over deadlock, and whats even worse, deadlocks the entire app so you cannot report and notice this deadlock state in the app itself, which makes you blind. I almost never had this problem with dispatch, you could fairly reliably guarantee and reason about some backup queues being able to detect these states and report the deadlock state.
IMO I would recommend not interacting with async/await as much as possible and stick to dispatch queues you can reason about far more easily.
Do you happen to have some links to information about the issues you mentioned?
Swift async/await has worked excellently for me so far. The biggest issue is that most libraries aren't updated to use it (and sometimes couldn't be, because they require Custom Actor Executors, which weren't available until Swift 5.9).
I'm not sure what you're doing to get into deadlocks, but when used as prescribed, I personally haven't run in to these issues.
Swift concurrency is still in a transitory period, and with that comes some warnings about how you can mix it with legacy concurrency primitives. i.e. not holding a lock across Task boundaries.
However, it's fairly well documented. There's a talk 'Swift concurrency: Behind the scenes' [1], that goes into detail on this. View from around the 25 min mark.
I've not really played around with async/await because I immediately found issues trying to replace my GCD code, swift still doesn't have the fine grained control GCD offers.
I replaced some of my other async code with Combine, which I do really like now, it's proving itself to be pretty solid
A lot of people are asking you how it's possible you're getting deadlocks, so I'll reply to all of them here: It's definitely possible without doing anything wrong in your own code, if you're calling poorly written code in other frameworks. See: https://forums.swift.org/t/deadlock-when-using-dispatchqueue...
The important quote:
> both Swift concurrency and Dispatch’s queues are serviced by same underlying pool of threads — Swift concurrency’s jobs just promise not to block on future work
What this means, is that if you're in a Swift concurrency context, and you dispatch_async work to a concurrent queue, then use a semaphore (or similar) to block on that work completing, then the thread pool implementation will not backfill the blocked thread. Crucially, this is true even if the code doing the semaphore hack is old ObjC code that used to work fine.
So if some older code you happen to be calling is doing something like:
func badIdea() {
let sem = DispatchSemaphore()
someConcurrentQueue.async {
doLongRunningThing(completion: { sem.signal() })
}
sem.wait()
}
and you happen to call `badIdea()` from all cores simultaneously, you'll deadlock.
Now, under normal pre-Swift-Concurrency circumstances, GCD would spawn a new thread to handle the queue.async block, which would free the semaphore (this leads to thread explosion, but at least not deadlocks.) But if the call to `badIdea()` happens to be done by a Swift Concurrency Task, then the thread pool gets a hint saying "don't worry, this thread will never block on future work", so it doesn't spawn a new thread to handle the dispatch_async, and you're hosed.
How exposed you are to this issue depends on what kind of code you're calling (third party, even code written by Apple) that may be doing this semaphore hack. You don't have to do this semaphore hack yourself, for this to be a problem. You just have to call into poorly written framework code which may be doing this.
Now, the answer to this problem is that "nobody should write code that does this", which is absolutely true, but it also is the case that there's a lot of code which does it anyway. A lot of people run into this function-coloring issue (which existed before `async/await` was a thing, completion-based functions have the exact same problem) and find themselves painted into a corner where they need to be synchronous, but they need to call asynchronous code, and using a semaphore works, so they just do it and ship it. Swift Concurrency rather silently changes the contract here so that stuff that used to be "merely" a bad idea, is now a deadlock.
This is exactly it, a lot of apple framework code under the hood is not safe and you don't notice it most of the time, but you get users reporting it and maybe reproduce it intermittently once a week if that.
Most apps are not as intense as ours, they are web app equivalents that just do a few http calls and display form data. Our app is pretty intense with gpu background jobs, image queries, ai models running, local db modifications and network uploads occurring, which is way more of a concurrency stress test than most. It acts like a local only desktop app with some optional internet features.
It’s the observability blocking that is the worst part. If we could observe deadlock states then it wouldn't be as bad.
async/await is deadlock-free if things are working correctly (and they mostly do) and you don't do anything that hinders forward progress. what are you doing with it?
IMO I would recommend not interacting with async/await as much as possible and stick to dispatch queues you can reason about far more easily.