What determines whether a Swift 5.5 Task initializer runs on the main thread?

I think the rule must be that a Task initializer in a MainActor method runs on the main thread.

And all methods of a view controller are MainActor methods by default; plus, I observe that if I declare test2 to be nonisolated, its Task operation runs on a background thread instead of the main thread.

My guess, then, is that this is an example of the rule that a Task initializer’s operation “inherits” from its context:

  • test2 is a MainActor method; it runs on the main thread, so the Task operation “inherits” that.

  • But test1 is not marked for any special thread. test1 itself runs on the main thread, because it is called on the main thread; but it is not marked to run on the main thread. Therefore its Task operation falls back to running on a background thread.

That’s my theory, anyway, But I find it curious that this rule is nowhere clearly enunciated in the relevant WWDC videos.

Moreover, even test2 is only a MainActor method in a sort of “weak” way. If it were really a MainActor method, you could not be able to call it from a background thread without await. But you can, as this version of the code shows:

func test1() {
    print("test1", Thread.isMainThread) // true
    Task {
        print("test1 task", Thread.isMainThread) // false
    }
}
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        test1()
        Task.detached {
            self.test2()
        }
    }

    func test2() {
        print("test2", Thread.isMainThread) // false
        Task {
            print("test2 task", Thread.isMainThread) // true
        }
    }
}

I find that truly weird, and I have some difficulty enunciating what rule would govern this relentless context-switching behavior, so I don’t regard the matter as settled.

Leave a Comment