Let’s say I have an async
function within a @MainActor
class that performs some slow, synchronous, CPU-intensive operation:
@MainActor
class MyClass {
// ...
func getFoo() async -> Foo {
let foo = // ... some slow, synchronous, CPU-intensive operation ...
return foo
}
// ...
}
How can I make my function run on a background thread (so that I don’t block the main thread)? I’ve seen two different approaches but am not sure which is correct (if either) as I’ve seen arguments against both of them.
Task.detached
func getFoo() async -> Foo { await Task.detached { let foo = // ... some slow, synchronous, CPU-intensive operation ... return foo }.value }
Continuation + GCD
func getFoo() async -> Foo { await withCheckedContinuation { continuation in DispatchQueue.global().async { let foo = // ... some slow, synchronous, CPU-intensive operation ... continuation.resume(returning: foo) } } }
Arguments Against Task.detached
[You] should not run long-running expensive, blocking operations in a concurrency context, regardless of if it's the main actor or not. Swift concurrency is a cooperative model, i.e. the functions that run in a concurrency context are expected to regularly suspend to give up control of their thread and give the runtime a chance to schedule other tasks on that thread.
Recall that with Swift, the language allows us to uphold a runtime contract that threads will always be able to make forward progress. It is based on this contract that we have built a cooperative thread pool to be the default executor for Swift. As you adopt Swift concurrency, it is important to ensure that you continue to maintain this contract in your code as well so that the cooperative thread pool can function optimally.
Swift concurrency: Behind the scenes (WWDC21)
If I’m understanding these two quotes correctly, slow, synchronous operations shouldn’t be done within Swift concurrency as they would block forward progress and that would violate the runtime contract. That being said, I would have assumed that the alternative would have been continuations + GCD, but I’ve also seen arguments against that.
Arguments Against Continuations + GCD
One should avoid using
DispatchQueue
at all.
I would avoid introducing GCD, as that does not participate in the cooperative thread pool and Swift concurrency will not be able to reason about the system resources, avoid thread explosion, etc.
So, all of that being said, what is the correct way of running an async
func on a background thread?