mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
68 lines
2.3 KiB
Markdown
68 lines
2.3 KiB
Markdown
# Sending closure risks causing data races
|
|
|
|
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.
|
|
|
|
For example:
|
|
|
|
```swift
|
|
class MyModel {
|
|
var count: Int = 0
|
|
|
|
func perform() {
|
|
Task {
|
|
self.update()
|
|
}
|
|
}
|
|
|
|
func update() { count += 1 }
|
|
}
|
|
```
|
|
|
|
The compiler diagnoses the capture of `self` in the task closure:
|
|
|
|
```
|
|
| class MyModel {
|
|
| func perform() {
|
|
| Task {
|
|
| `- error: passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
|
|
| self.update()
|
|
| `- note: closure captures 'self' which is accessible to code in the current task
|
|
| }
|
|
| }
|
|
```
|
|
|
|
This code is invalid because the task that calls `perform()` runs concurrently with the task that calls `update()`. The `MyModel` type does not conform to `Sendable`, and it has unprotected mutable state that both concurrent tasks could access simultaneously.
|
|
|
|
To eliminate the risk of data races, all tasks that can access the `MyModel` instance must be serialized. The easiest way to accomplish this is to isolate `MyModel` to a global actor, such as the main actor:
|
|
|
|
```swift
|
|
@MainActor
|
|
class MyModel {
|
|
func perform() {
|
|
Task {
|
|
self.update()
|
|
}
|
|
}
|
|
|
|
func update() { ... }
|
|
}
|
|
```
|
|
|
|
This resolves the data race because the two tasks that can access the `MyModel` value must switch to the main actor to access its state and methods.
|
|
|
|
The other approach to resolving the error is to ensure that only one task has access to the `MyModel` value at a time. For example:
|
|
|
|
```swift
|
|
class MyModel {
|
|
static func perform(model: sending MyModel) {
|
|
Task {
|
|
model.update()
|
|
}
|
|
}
|
|
|
|
func update() { ... }
|
|
}
|
|
```
|
|
|
|
This code is safe from data races because the caller of `perform` cannot access the `model` parameter again after the call. The `sending` parameter modifier indicates that the implementation of the function sends the value to a different concurrency domain, so it's no longer safe to access the value in the caller. This ensures that only one task has access to the value at a time.
|