Task does not execute on background queue when it has synchronous call first

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 { }.

I believe that usage of Task.detached is strongly discouraged, but is that the only way to run Task on background queue?

According to who? It was specifically added to run code outside the original actor.

According to documentation: Apple Developer Documentation

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?

It's still unclear to me how cancellation propagation is supposed to work (and whether it's buggy or not), so I can't answer that.

Terms of Service

Privacy Policy

Cookie Policy