I'm trying out the new async/await stuff. My goal here is to run the test()
method in the background, so I use Task.detached
; but during test()
I need to make a call on the main thread, so I'm using MainActor.
(I realize that this may look convoluted in isolation, but it's pared down from a much better real-world case.)
Okay, so test code looks like this (in a view controller):
override func viewDidLoad() {
super.viewDidLoad()
Task.detached(priority: .userInitiated) {
await self.test()
}
}
@MainActor func getBounds() async -> CGRect {
let bounds = self.view.bounds
return bounds
}
func test() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.getBounds()
print("test 2", Thread.isMainThread) // true
}
The first print
says I'm not on the main thread. That's what I expect.
But the second print
says I am on the main thread. That isn't what I expect.
It feels as if I've mysteriously fallen back into the main thread just because I called a MainActor function. I thought I would be waiting for the main thread and then resuming in the background thread I was already on.
Is this a bug, or are my expectations mistaken? If the latter, how do I step out to the main thread during await
but then come back to the thread I was on? I thought this was exactly what async/await would make easy...?
(I can "solve" the problem, in a way, by calling Task.detached
again after the call to getBounds
; but at that point my code looks so much like nested GCD that I have to wonder why I'm using async/await at all.)
Maybe I'm being premature but I went ahead and filed this as a bug: https://bugs.swift.org/browse/SR-14756.
More notes:
I can solve the problem by replacing
let bounds = await self.getBounds()
with
async let bounds = self.getBounds()
let thebounds = await bounds
But that seems unnecessarily elaborate, and doesn't convince me that the original phenomenon is not a bug.
I can also solve the problem by using actors, and this is starting to look like the best approach. But again, that doesn't persuade me that the phenomenon I'm noting here is not a bug.
I'm more and more convinced that this is a bug. I just encountered (and reported) the following:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
async {
print("howdy")
await doSomeNetworking()
}
}
func doSomeNetworking() async {
print(Thread.isMainThread)
}
This prints howdy
and then the second print
prints true
. But if we comment out the first print, the remaining (second) print
prints false
!
How can merely adding or removing a print statement change what thread we're on? Surely that's not intended.
Thread.isMainThread
is mistaken somehow, it isn't. I know because if I speak ofself.view.bounds
withintest
before theawait
, I crash in the main thread checker, but if I speak of it after theawait
, I don't crash. We really are context switching here, and I don't know why. – Ably.userInitiated
priority, which is strangely high for expensive work. This could be influencing some of the scheduling decisions. Maybe you should do anotherasyncDetached
with lower priority before doing expensive work? – BrownleydetachedAsync
vs.async
, the priority is irrelevant to the result I'm describing. Try it yourself. – Ably