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.