Eliminate data races using Swift Concurrency

RSS for tag

Discuss the WWDC22 Session Eliminate data races using Swift Concurrency

Posts under wwdc2022-110351 tag

2 Posts
Sort by:
Post not yet marked as solved
2 Replies
45 Views
This code can be compiled as command line tool for macOS. import Foundation @main struct App {     static var counter = 0     static func main() async throws {         print("Thread: \(Thread.current)")         let task1 = Task { @MainActor () -> Void in             print("Task1 before await Task.yield(): \(Thread.current)")             await Task.yield()             print("Task1 before await increaseCounter(): \(Thread.current)")             await increaseCounter()             print("Task1 after await increaseCounter(): \(Thread.current)")         }         let task2 = Task { @MainActor () -> Void in             print("Task2 before await Task.yield(): \(Thread.current)")             await Task.yield()             print("Task2 before await decreaseCounter(): \(Thread.current)")             await decreaseCounter()             print("Task2 after await decreaseCounter(): \(Thread.current)")         }         _ = await (task1.value, task2.value)         print("Final counter value: \(counter)")     }     static func increaseCounter() async {         for i in 0..<999 {             counter += 1             print("up step \(i), counter: \(counter), thread: \(Thread.current)")             await Task.yield()         }     }     static func decreaseCounter() async {         for i in 0..<999 {             counter -= 1             print("down step \(i), counter: \(counter), thread: \(Thread.current)")             await Task.yield()         }     } } My understanding is: static func main() async throws inherits MainActor async context, and should always run on the main thread (and it really seems that it does so) Task is initialized by the initializer, so it inherits the actor async context, so I would expect that will run on the main thread. Correct? Moreover, the closure for Task is annotated by @MainActor, so I would even more expect it will run on the main thread. I would expect that static func main() async throws inherits MainActor async context and will prevent data races, so the final counter value will always be zero. But it is not. Both task1 and task2 really start running on the main thread, however the async functions increaseCounter() and decreaseCounter() run on other threads than the main thread, so the Task does not prevent data races, while I would expect it. When I annotate increaseCounter() and decreaseCounter() by @MainActor then it works correctly, but this is what I do not want to do, I would expect that Task will do that. Can anyone explain, why this works as it does, please?
Posted
by hibernat.
Last updated
.
Post not yet marked as solved
3 Replies
324 Views
I have a Networking client that contains some shared mutable state. I decided to make this class an actor to synchronize access to this state. Since it's a networking client, it needs to make calls into URLSession's async data functions. struct MiddlewareManager { var middleware: [Middleware] = [] func finalRequest(for request: URLRequest) -&gt; URLRequest { ... } } actor Networking { var middleware = MiddlewareManager() var session: URLSession func data(from request: URLRequest) async throws -&gt; (Data, URLResponse) { let finalRequest = middleware.finalRequest(for: request) let (data, response) = try await session.data(for: finalRequest) let finalResponse = middleware.finalResponse(for: response) // ... return (data, finalResponse) } } Making a network request could be a long running operation. From my understanding long running or blocking operations should be avoided inside of actors. I'm pretty sure this is non blocking due to the await call, the task just becomes suspended and the thread continues on, but it will inherit the actor's context. As long URLSession creates a new child task then this is ok, since the request isn't actually running on the actor's task? My alternative approach after watching the tagged WWDC 2022 videos was to add the nonisolated tag to the function call to allow the task to not inherit the actor context, and only go to the actor when needed: nonisolated func data(from request: URLRequest) async throws -&gt; (Data, URLResponse) { let finalRequest = await middleware.finalRequest(for: request) let (data, response) = try await session.data(for: finalRequest) let finalResponse = await middleware.finalResponse(for: response) // ... return (data, finalResponse) } Is this the proper or more correct way to solve this?
Posted
by Kylelawl.
Last updated
.