When I mark a protocol with async functions with @MainActor and it's conformance does not specify it's functions are async, it is not executing on main thread. I don't know if this is a bug or not.
For example, given the following protocol and implementation:
@MainActor
protocol MyMainActorProtocol {
func doStuff() async
}
struct MyMainActorStruct: MyMainActorProtocol {
nonisolated init() {}
func doStuff() {
print(">>> \(Thread.isMainThread)")
}
}
When I execute this code:
Task {
let dependency: MyMainActorProtocol = MyMainActorStruct()
print(">>> \(Thread.isMainThread)")
await dependency.doStuff()
}
both print statements run in a background thread.
But with the following adjustments of either
@MainActor
protocol MyMainActorProtocol {
func doStuff()
}
struct MyMainActorStruct: MyMainActorProtocol {
nonisolated init() {}
func doStuff() {
print(">>> \(Thread.isMainThread)")
}
}
or
@MainActor
protocol MyMainActorProtocol {
func doStuff() async
}
struct MyMainActorStruct: MyMainActorProtocol {
nonisolated init() {}
func doStuff() async {
print(">>> \(Thread.isMainThread)")
}
}
I get the correct result, where the body of the function inside the struct runs in the main thread.
Is this expected behavior? What is the explanation behind it.
According to the global actor inference rules,
So provided you put them in the same source file,
MyMainActorStruct
should beMainActor
-isolated too, and so isdoStuff
.As a result, you don't get errors when you try to e.g. initialise a
UIView
indoStuff
. However, when you do try to do that at runtime, you get logs in the console saying "UI API called on a background thread". This should not happen. This behaviour is clearly a bug.Note that even if you add
@MainActor
toMyMainActorStruct
, or even todoStuff
, this behaviour still remains.To find out how this happened, I compared the SIL generated by the following codes (See the full SIL on godbolt.org):
The former has a
hop_to_executor %4 : $MainActor
line indoStuff
, while the latter lacks it.This is very similar to a previous (fixed) bug that happens in a very similar situation. That bug was about
actor
s implementingasync
protocol requirements with non-async
code, e.g.and the same actor-hop was missing as well. Although this bug was fixed, it could very likely be that they also forgot to emit an actor hop in the case of global actors.