user29816220
user29816220

Reputation: 21

swift 6 warnings "Passing closure as a 'sending' parameter risks causing data races"

My code get the warning below at line Task{}. Basically, I want to call an async function where I don't want to wait for the result to come back, meaning next line of code should be executed right away. It is like a fire-and-ignore kind of execution. How do we do that with Swift 6?

I have tried with Task.detached{}, but that don't help

"Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure; this is an error in the Swift 6 language mode"

func run() async {
  Task {
      _ = await self.fetch()
  }
  doOtherWork()
}

func fetch() async {}
func doOtherWork() {}

Upvotes: 0

Views: 90

Answers (2)

Rob
Rob

Reputation: 438457

This is described the swiftlang github repository under swift / userdocs / diagnostics / sending-closure-risks-data-race:

If a type does not conform to Sendable, the compiler enforces that each instance of that type is only accessed by one concurrency domain at a time. The compiler also prevents you from capturing values in closures that are sent to another concurrency domain if the value can be accessed from the original concurrency domain too.

So, you have a number of options, including:

  1. Make self conform to Sendable.

    final class Foo: Sendable {
        func run() async {
            Task {
                _ = await self.fetch()
            }
            doOtherWork()
        }
    
        func fetch() async {}
    
        func doOtherWork() {}
    }
    

    (As an aside, if you go down that road, you must not just use @unchecked Sendable without actually going through the work of making it thread-safe. Make sure you synchronize all access to any mutable state, if any.)

  2. Isolate the functions to a global actor, such @MainActor.

    @MainActor
    class Foo {
        func run() async {
            Task {
                _ = await self.fetch()
            }
            doOtherWork()
        }
    
        func fetch() async {…}
        func doOtherWork() {…}
    }
    

    If you don’t want to isolate these to the main actor, you can use your own global actor, too. E.g.:

    @globalActor
    actor FooActor {
        static let shared = FooActor()
    }
    
    @FooActor
    class Foo {
        func run() async {
            Task {
                _ = await self.fetch()
            }
            doOtherWork()
        }
    
        func fetch() async {}
    
        func doOtherWork() {}
    }
    
  3. Make this an actor:

    actor Foo {
        func run() async {
            Task {
                _ = await self.fetch()
            }
            doOtherWork()
        }
    
        func fetch() async {…}
        func doOtherWork() {…}
    }
    

Bottom line, you want to make sure that either (a) self is Sendable; or (b) the Task is isolated to the same concurrency context that self is.

Upvotes: 0

timbre timbre
timbre timbre

Reputation: 14004

Generally you have 2 directions in solving this issue:

  • Make sure your class is generally thread-safe, and get it to conform to Sendable protocol. You'd need to make sure the class doesn't contain mutable reference type properties, etc (all rules are explained here).

  • Or, if the class is not thread-safe, and you cannot guarantee it to be such, make it an actor, so you can add proper thread isolations where the object may mutate.

It's somehow more common to go the actor route, since it's typically easier, than conforming to Sendable. But I think it should really depends on whether it makes sense for the class to be Sendable, or does it make sense for it to be an actor. More details: Data Race Safety.

There are also 2 temporary workarounds, to pass the compilation, if you know your code requires bigger refactoring:

  • If the compiler doesn't see class as thread-safe, but you are sure it is (e.g. you use other methods to ensure that mutations on the class properties can be done in a thread-safe manner), you can use @unchecked Sendable .

  • You also have an option to silence the error by using @preconcurrency annotation.

Upvotes: 0

Related Questions