Do escaping closure that are passed to actor method inherit actor isolation? Or they are non isolated?
For instance:
actor MyActor {
func method(_ closure: @escaping () async -> Void) {
await closure()
}
}
With what isolation will closure be created? In my simple test, seems that closure inherit it's context isolation on allocation
actor MyActor {
func method(_ closure: @escaping () async -> Void) async {
print("in actor method: ", Thread.current)
await closure()
print("in actor method: ", Thread.current)
}
}
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let actor = MyActor()
Task {
print("in task closure: ", Thread.current)
await actor.method {
print("in actor closure: ", Thread.current)
try? await Task.sleep(nanoseconds: 1_000_000_000)
print("in actor closure: ", Thread.current)
}
print("in task closure: ", Thread.current)
}
return true
}
Outputs:
in task closure: <_NSMainThread: 0x283604380>{number = 1, name = main}
in actor method: <NSThread: 0x283654400>{number = 5, name = (null)}
in actor closure: <_NSMainThread: 0x283604380>{number = 1, name = main}
in actor closure: <_NSMainThread: 0x283604380>{number = 1, name = main}
in actor method: <NSThread: 0x283654400>{number = 5, name = (null)}
in task closure: <_NSMainThread: 0x283604380>{number = 1, name = main}
I know that it's it proper proof of hypothesis, therefore I'm asking: Is there are any proposals or statements, which describe what isolation async closure do get?
Yes, the closure will use the same actor context from which it was formed (the main actor in this example):
As SE-0306 says
A closure that is not
@Sendable
cannot escape the concurrency domain in which it was formed. Therefore, such a closure will be actor-isolated if it is formed within an actor-isolated context.
If you do not want the closure running on the actor from which it is formed, you have a few options:
You can make the closure @Sendable
. As SE-0306 says:
Actors [specify] that a
@Sendable
closure (described inSendable
and@Sendable
closures, and used in the definition ofdetach
in the Structured Concurrency proposal) is always non-isolated.
Thus,
actor MyActor {
func method(_ closure: @escaping @Sendable () async -> Void) async {
...
}
}
That results in the closure being non-isolated (i.e., not running on the actor where the closure was created).
You can also explicitly specify the closure’s actor. You could specify a global actor:
@globalActor
public struct SomeGlobalActor {
public actor SomeOtherActor { }
public static let shared = SomeOtherActor()
}
actor MyActor {
func method(_ closure: @escaping @SomeGlobalActor () async -> Void) async {
...
}
}