You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# Calling an actor-isolated method from a synchronous nonisolated context
2
+
3
+
Calls to actor-isolated methods from outside the actor must be done asynchronously. Otherwise, access to actor state can happen concurrently and lead to data races. These rules also apply to global actors like the main actor.
4
+
5
+
For example:
6
+
7
+
```swift
8
+
@MainActor
9
+
classMyModel {
10
+
funcupdate() { ... }
11
+
}
12
+
13
+
funcrunUpdate(model: MyModel) {
14
+
model.update()
15
+
}
16
+
```
17
+
18
+
Building the above code produces an error about calling a main actor isolated method from outside the actor:
19
+
20
+
```
21
+
| func runUpdate(model: MyModel) {
22
+
| model.update()
23
+
| `- error: call to main actor-isolated instance method 'update()' in a synchronous nonisolated context
24
+
| }
25
+
```
26
+
27
+
The `runUpdate` function doesn't specify any actor isolation, so it is `nonisolated` by default. `nonisolated` methods can be called from any concurrency domain. To prevent data races, `nonisolated` methods cannot access actor isolated state in their implementation. If `runUpdate` is called from off the main actor, calling `model.update()` could mutate main actor state at the same time as another task running on the main actor.
28
+
29
+
To resolve the error, `runUpdate` has to make sure the call to `model.update()` is on the main actor. One way to do that is to add main actor isolation to the `runUpdate` function:
30
+
31
+
```swift
32
+
@MainActor
33
+
funcrunUpdate(model: MyModel) {
34
+
model.update()
35
+
}
36
+
```
37
+
38
+
Alternatively, if the `runUpdate` function is meant to be called from arbitrary concurrent contexts, create a task isolated to the main actor to call `model.update()`:
Concurrency checking prohibits mutable global and static variables that are `nonisolated` because they can be accessed from arbitrary concurrency domains at once and lead to data races.
4
+
5
+
For example:
6
+
7
+
```swift
8
+
structConstants {
9
+
staticvar value =10
10
+
}
11
+
```
12
+
13
+
Building this code with complete concurrency checking will point out the unsafe static variable:
14
+
15
+
```
16
+
| struct Constants {
17
+
| static var value = 10
18
+
| |- error: static property 'value' is not concurrency-safe because it is nonisolated global shared mutable state
19
+
| |- note: convert 'value' to a 'let' constant to make 'Sendable' shared state immutable
20
+
| |- note: add '@MainActor' to make static property 'value' part of global actor 'MainActor'
21
+
| `- note: disable concurrency-safety checks if accesses are protected by an external synchronization mechanism
22
+
```
23
+
24
+
If the type of the variable conforms to `Sendable` and the value is never changed, a common fix is to change the `var` to a `let` to make the state immutable. Immutable state is safe to access concurrently!
25
+
26
+
If you carefully access the global variable in a way that cannot cause data races, such as by wrapping all accesses in an external synchronization mechanism like a lock or a dispatch queue, you can apply `nonisolated(unsafe)` to opt out of concurrency checking:
27
+
28
+
```swift
29
+
nonisolated(unsafe) staticvar value =10
30
+
```
31
+
32
+
Now consider a static variable with a type that does not conform to `Sendable`:
33
+
34
+
```swift
35
+
classMyModel {
36
+
staticlet shared =MyModel()
37
+
38
+
// mutable state
39
+
}
40
+
```
41
+
42
+
This code is also diagnosed under complete concurrency checking. Even though the `shared` variable is a `let` constant, the `MyModel` type is not `Sendable`, so it could have mutable stored properties. A common fix in this case is to isolate the variable to the main actor:
43
+
44
+
```swift
45
+
classMyModel {
46
+
@MainActor
47
+
staticlet shared =MyModel()
48
+
}
49
+
```
50
+
51
+
Alternatively, isolate the `MyModel` class to the main actor, which will also make the type `Sendable` because the main actor protects access to all mutable state:
`@Sendable` closures can be called multiple times concurrently, so any captured values must also be safe to access concurrently. To prevent data races, the compiler prevents capturing mutable values in a `@Sendable` closure.
4
+
5
+
For example:
6
+
7
+
```swift
8
+
funccallConcurrently(
9
+
_closure: @escaping@Sendable () ->Void
10
+
) { ... }
11
+
12
+
funccapture() {
13
+
var result =0
14
+
result +=1
15
+
16
+
callConcurrently {
17
+
print(result)
18
+
}
19
+
}
20
+
```
21
+
22
+
The compiler diagnoses the capture of `result` in a `@Sendable` closure:
23
+
24
+
```
25
+
| callConcurrently {
26
+
| print(result)
27
+
| `- error: reference to captured var 'result' in concurrently-executing code
28
+
| }
29
+
| }
30
+
```
31
+
32
+
Because the closure is marked `@Sendable`, the implementation of `callConcurrently` can call `closure` multiple times concurrently. For example, multiple child tasks within a task group can call `closure` concurrently:
33
+
34
+
```swift
35
+
funccallConcurrently(
36
+
_closure: @escaping@Sendable () ->Void
37
+
) {
38
+
Task {
39
+
awaitwithDiscardingTaskGroup { group in
40
+
for_in0..<10 {
41
+
group.addTask {
42
+
closure()
43
+
}
44
+
}
45
+
}
46
+
}
47
+
}
48
+
```
49
+
50
+
If the type of the capture is `Sendable` and the closure only needs the value of the variable at the point of capture, resolve the error by explicitly capturing the variable by value in the closure's capture list:
51
+
52
+
```swift
53
+
funccapture() {
54
+
var result =0
55
+
result +=1
56
+
57
+
callConcurrently { [result] in
58
+
print(result)
59
+
}
60
+
}
61
+
```
62
+
63
+
This strategy does not apply to captures with non-`Sendable` type. Consider the following example:
64
+
65
+
```swift
66
+
classMyModel {
67
+
funclog() { ... }
68
+
}
69
+
70
+
funccapture(model: MyModel) async {
71
+
callConcurrently {
72
+
model.log()
73
+
}
74
+
}
75
+
```
76
+
77
+
The compiler diagnoses the capture of `model` in a `@Sendable` closure:
78
+
79
+
```
80
+
| func capture(model: MyModel) async {
81
+
| callConcurrently {
82
+
| model.log()
83
+
| `- error: capture of 'model' with non-sendable type 'MyModel' in a '@Sendable' closure
84
+
| }
85
+
| }
86
+
```
87
+
88
+
If a type with mutable state can be referenced concurrently, but all access to mutable state happens on the main actor, isolate the type to the main actor and mark the methods that don't access mutable state as `nonisolated`:
89
+
90
+
```swift
91
+
@MainActor
92
+
classMyModel {
93
+
nonisolatedfunclog() { ... }
94
+
}
95
+
96
+
funccapture(model: MyModel) async {
97
+
callConcurrently {
98
+
model.log()
99
+
}
100
+
}
101
+
```
102
+
103
+
The compiler will guarantee that the implementation of `log` does not access any main actor state.
104
+
105
+
If you manually ensure data-race safety, such as by using an external synchronization mechanism, you can use `nonisolated(unsafe)` to opt out of concurrency checking:
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.
4
+
5
+
For example:
6
+
7
+
```swift
8
+
classMyModel {
9
+
var count: Int=0
10
+
11
+
funcperform() {
12
+
Task {
13
+
self.update()
14
+
}
15
+
}
16
+
17
+
funcupdate() { count +=1 }
18
+
}
19
+
```
20
+
21
+
The compiler diagnoses the capture of `self` in the task closure:
22
+
23
+
```
24
+
| class MyModel {
25
+
| func perform() {
26
+
| Task {
27
+
| `- error: passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure
28
+
| self.update()
29
+
| `- note: closure captures 'self' which is accessible to code in the current task
30
+
| }
31
+
| }
32
+
```
33
+
34
+
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.
35
+
36
+
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:
37
+
38
+
```swift
39
+
@MainActor
40
+
classMyModel {
41
+
funcperform() {
42
+
Task {
43
+
self.update()
44
+
}
45
+
}
46
+
47
+
funcupdate() { ... }
48
+
}
49
+
```
50
+
51
+
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.
52
+
53
+
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:
54
+
55
+
```swift
56
+
classMyModel {
57
+
staticfuncperform(model: sending MyModel) {
58
+
Task {
59
+
model.update()
60
+
}
61
+
}
62
+
63
+
funcupdate() { ... }
64
+
}
65
+
```
66
+
67
+
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.
0 commit comments