swiftasync-awaitconcurrencyactor

Why I can get access to variable from multiple Tasks in Actor?


Why do these two Task run in parallel inside Actor? Doesn't this create a data race after multiple value variable accesses? Why didn't the compiler forbid me?

import SwiftUI

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().task {
                await Actor1().performAsyncTasks()
            }
        }
    }
}

class Actor1 {
    var value = 0 // Data race?
    
    func performAsyncTasks() async {
        Task {
            for i in 1...10_000 {
                print("In Task 1: \(i)")
                value += 1 // Data race?
            }
        }
        
        Task {
            for i in 1...10_000 {
                print("In Task 2: \(i)")
                value += 1 // Data race?
            }
        }
    }
}

Solution

  • Yes, this is a data race. You can see this if you turn on the Thread Sanitizer under the Scheme's Diagnostics menu. In my tests, I get:

    Data race in (1) suspend resume partial function for closure #1 
    @Sendable () async -> () in test.Actor1.performAsyncTasks() async -> () 
    at 0x112226c00
    

    The problem is that Actor1 is not an actor. It's a class. If you replace class with actor, then the problem will go away. This is the point of actors. The compiler allows you to access a class this way because there are no low-level data race protections on classes. That's why actors were added to the language.

    Tasks inherit the current actor's context, so if Actor1 were an actor, both Tasks would cooperate within the actor's executor and be run sequentially. The executor can only switch tasks at an await keyword, and there aren't any in your tasks.