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,
MyMainActorStructshould beMainActor-isolated too, and so isdoStuff.As a result, you don't get errors when you try to e.g. initialise a
UIViewindoStuff. 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
@MainActortoMyMainActorStruct, 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 : $MainActorline 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
actors implementingasyncprotocol requirements with non-asynccode, 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.