Reputation: 5779
Consider this simple example, where we have an Actor and a Class which holds a reference to that Actor:
actor MyActor
{
func doWork() -> Int {
return 42
}
}
class MyClass
{
let worker: MyActor = MyActor()
func swift6IsJustGreat()
{
Task {
let number: Int = await worker.doWork() // ERROR: Capture of 'self' with non-sendable type 'MyClass' in a `@Sendable` closure
print(number)
}
}
}
Xcode 16 Beta 1, when using Swift 6 Language Mode, shows this error on the line where we attempt to use the actor:
Capture of 'self' with non-sendable type 'MyClass' in a `@Sendable` closure
I understand the error. worker
is a property of MyClass
and the Task closure is capturing the instance of MyClass
, which is not Sendable
. Got it. But then...how the hell do I use anything in concurrency unless it's a global?
If my actor is an iVar on a non-Sendable class, how can I ever call anything on the actor?
Upvotes: 13
Views: 14576
Reputation: 29614
Floating Task
are the source of many concurrency issues. The idea that an operation should be "let lose" is an old way of thinking you should just add the word async
to the method.
import Foundation
actor MyActor
{
func doWork() -> Int {
return 42
}
}
class MyClass
{
let worker: MyActor = MyActor()
func swift6IsJustGreat() async
{
let number: Int = await worker.doWork()
print(number)
}
}
Upvotes: 1
Reputation: 299565
Capture worker
into the Task rather than self
:
Task { [worker] in // This makes a local copy of `self.worker`
let number: Int = await worker.doWork() // No problem
print(number)
}
The Task does not actually need self
. It doesn't do anything with it, and since worker
is a let
property, it is impossible for it to change between the time Task
begins and when worker
is used. (If it were var
, then it would be even more important that it be captured rather than self
, since it could change in a thread-unsafe way.)
An Actor is always Sendable, so can always be captured by a Task.
Upvotes: 21