I discovered that the closure of Task is executed on main thread when the print is not commented. When it is commented like in the code below, it executes on the background queue.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Button("Tap me") {
Task {
//print("starting")
await count()
print("done")
}
}
}
}
}
func count() async {
for _ in 0...100_000 {
let num = Int.random(in: 0...10)
print(num)
}
}
Which behaviour is correct? I want to execute the code on background queue, but it seems like it is glitching.
As I understand it Task { } should always run on the current actor. In your case, that should be the MainActor where SwiftUI's user interactions run. So the behavior you're seeing sounds like a bug, though the opposite of what you're thinking: both forms should run on the MainActor.
If you want to run your work in the background you need to use Task.detached { }.
Don’t use a detached task if it’s possible to model the operation using structured concurrency features like child tasks. Child tasks inherit the parent task’s priority and task-local storage, and canceling a parent task automatically cancels all of its child tasks. You need to handle these considerations manually with a detached task.
You need to keep a reference to the detached task if you want to cancel it by calling the Task.cancel() method. Discarding your reference to a detached task doesn’t implicitly cancel that task, it only makes it impossible for you to explicitly cancel the task.
But on the other hand, I actually wanna run a task in a new context away from MainActor so I guess it makes sense. Does it mean that whenever we are trying to run background operation from main thread, we need to use Task.detached and be sure it is being called from MainActor as otherwise it would break the whole context and cancellation propagation?
Detached Task doesn't inherit actor context, no matter it is MainActor or any other. If you use Task.detached {}, there will be no error propagation or cancellation, because it is an unstructured Task.
Though, you can make an actor that will be responsible for networking. URLSession has also adopted new concurrency features. When used, all URLSession tasks will be performed in background. When the network requests is finished, Task {} body inside Button continue its execution in main thread.
But to be clear: currently there no explicit way to run structure task in system provided background actor or executor, because we only have one global actor – MainActor. To run task explicitly on concrete actor, we can write Task { @MainActor in ... }. Maybe in future besides MainActor some kind of global actor will appear, and something like this can be written: Task { @BackgroundActor in ... }
One another coming feature is custom executors.
For now, you can create your own global Actor using GlobalActor protocol or use system provided API, that do their work in background.