onthemoon
onthemoon

Reputation: 3462

@MainActor is not guaranteeing to execute code in the main thread

I'm using the native ASWebAuthenticationSession to do a web-view based authentication and I 'm getting this runtime warning with corresponding console output:

Main Thread Checker: UI API called on a background thread: -[UIWindow init] PID: 15042, TID: 2317190, Thread name: (none), Queue name: com.apple.root.user-initiated-qos.cooperative, QoS: 25

enter image description here

So I have added the @MainActor attribute to the method but it has no effect. still getting the same warning and same console output:

Main Thread Checker: UI API called on a background thread: -[UIWindow init] PID: 15476, TID: 2328029, Thread name: (none), Queue name: com.apple.root.user-initiated-qos.cooperative, QoS: 25

enter image description here

Am I missing something? where I can find

Upvotes: 3

Views: 1827

Answers (1)

Gordonium
Gordonium

Reputation: 3497

Synchronous methods run on the same thread as the caller regardless of any actor annotations. E.g:

func foo() {
    // this will run on whichever thread it's called from
}

@MainActor
func foo2() {
    // this will ignore @MainActor and also run on the same thread it's called from. 
    // if the caller is the main thread, it will run on the main thread.
    // if the caller is a background thread, it will run on the background thread.
}

When I first started learning about actors I thought foo2 would be guaranteed to run on the main thread given it's annotated with @MainActor and the main actor's executor uses the main thread. What I didn't realise was that this only happens if the method is async. E.g.:

@MainActor
func foo3() async {
    // This is guaranteed to run on the main thread regardless of the caller's thread
}

Because foo3 is marked as async, the call site's Task can suspend execution and give up its thread. This allows foo3 to 'hop' onto a different actor if needed. In this example, foo3 is explicitly annotated with @MainActor so it inherits that context.

Even if we used a method that wasn't annotated explicitly, it might still implicitly inherit an actor context. E.g.

@MainActor
class Bar {
    func foo4() async {
        // this implicitly inherits the actor context from `Bar`
    }
}

So foo4 above would be isolated to the main actor and run on the main thread. But, if we removed async then it would run on the same thread as the caller and essentially ignore Bar's @MainActor annotation.

If an async method isn't isolated to an actor, it will run on the default executor, which uses a pool of background threads. This means that it can run on a different thread even if it was called from the main thread. E.g.:

// inherits no actor context
func foo5() async {
    // this will run on a background thread (even when called from the main thread)
}

So, to answer your question directly: the presentationAnchor(...) method is ignoring the @MainActor annotation because the method is synchronous. It will run on the same thread as the call site, which in this case is a background thread.

To isolate it to the main actor and use the main thread, you could either:

i) keep the @MainActor annotation and mark the method with async. Or, if you can't modify the call site, ii) manually place the code within the method on the main actor e.g. Task { @MainActor ... }.

Upvotes: 6

Related Questions