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
Make ActorQueue tasks execute on the target actor's execution context (#9)
* Make ActorQueue tasks execute on the target actor's execution context
* Documentation and naming pass
* Add comments to aid a consumer if they hit the unowned crash
* Review feedback
Co-authored-by: Michael Bachand <bachand.michael@gmail.com>
@@ -43,101 +45,87 @@ While [actors](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#
43
45
44
46
### Executing asynchronous tasks in FIFO order
45
47
46
-
Use a `FIFOQueue` to execute asynchronous tasks enqueued from a nonisolated context in FIFO order. Tasks sent to one of these queues are guaranteed to begin _and end_ executing in the order in which they are enqueued.
47
-
48
-
```swift
49
-
let queue =FIFOQueue()
50
-
queue.async {
51
-
/*
52
-
`async` context that executes after all other enqueued work is completed.
53
-
Work enqueued after this task will wait for this task to complete.
54
-
*/
55
-
try?await Task.sleep(nanoseconds: 1_000_000)
56
-
}
57
-
queue.async {
58
-
/*
59
-
This task begins execution once the above one-second sleep completes.
60
-
*/
61
-
}
62
-
await queue.await {
63
-
/*
64
-
`async` context that can return a value or throw an error.
65
-
Executes after all other enqueued work is completed.
66
-
Work enqueued after this task will wait for this task to complete.
67
-
*/
68
-
}
69
-
```
48
+
Use a `FIFOQueue` to execute asynchronous tasks enqueued from a nonisolated context in FIFO order. Tasks sent to one of these queues are guaranteed to begin _and end_ executing in the order in which they are enqueued. A `FIFOQueue` executes tasks in a similar manner to a `DispatchQueue`: enqueued tasks executes atomically, and the program will deadlock if a task executing on a `FIFOQueue` awaits results from the queue on which it is executing.
70
49
71
-
With a `FIFOQueue` you can easily execute asynchronous tasks from a nonisolated context in FIFO order:
50
+
A `FIFOQueue` can easily execute asynchronous tasks from a nonisolated context in FIFO order:
FIFO execution has a key downside: the queue must wait for all previously enqueued work – including suspended work – to complete before new work can begin. If you desire new work to start when a prior task suspends, utilize an `ActorQueue`.
95
87
96
-
Use an `ActorQueue` to send ordered asynchronous tasks from a nonisolated context to an `actor` instance. Tasks sent to one of these queues are guaranteed to begin executing in the order in which they are enqueued. Ordering of execution is guaranteed up until the first [suspension point](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID639) within the called `actor` code.
88
+
### Sending ordered asynchronous tasks to Actors from a nonisolated context
97
89
98
-
```swift
99
-
let queue =ActorQueue()
100
-
queue.async {
101
-
/*
102
-
`async` context that executes after all other enqueued work has begun executing.
103
-
Work enqueued after this task will wait for this task to complete or suspend.
104
-
*/
105
-
try?await Task.sleep(nanoseconds: 1_000_000)
106
-
}
107
-
queue.async {
108
-
/*
109
-
This task begins execution once the above task suspends due to the one-second sleep.
110
-
*/
111
-
}
112
-
await queue.await {
113
-
/*
114
-
`async` context that can return a value or throw an error.
115
-
Executes after all other enqueued work has begun executing.
116
-
Work enqueued after this task will wait for this task to complete or suspend.
117
-
*/
118
-
}
119
-
```
90
+
Use an `ActorQueue` to send ordered asynchronous tasks to an `actor`’s isolated context from nonisolated or synchronous contexts. Tasks sent to an actor queue are guaranteed to begin executing in the order in which they are enqueued. However, unlike a `FIFOQueue`, execution order is guaranteed only until the first [suspension point](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#ID639) within the enqueued task. An `ActorQueue` executes tasks within the its adopted actor’s isolated context, resulting in `ActorQueue` task execution having the same properties as `actor` code execution: code between suspension points is executed atomically, and tasks sent to a single `ActorQueue` can await results from the queue without deadlocking.
91
+
92
+
An instance of an `ActorQueue` is designed to be utilized by a single `actor` instance: tasks sent to an `ActorQueue` utilize the isolated context of the queue‘s adopted `actor` to serialize tasks. As such, there are a few requirements that must be met when dealing with an `ActorQueue`:
93
+
1. The lifecycle of any `ActorQueue` should not exceed the lifecycle of its `actor`. It is strongly recommended that an `ActorQueue` be a `let` constant on the adopted `actor`. Enqueuing a task to an `ActorQueue` isntance after its adopted `actor` has been deallocated will result in a crash.
94
+
2. An `actor` utilizing an `ActorQueue` should set the adopted execution context of the queue to `self` within the `actor`’s `init`. Failing to set an adopted execution context prior to enqueuing work on an `ActorQueue` will result in a crash.
120
95
121
-
With an `ActorQueue`you can easily begin execution of asynchronous tasks from a nonisolated context in order:
96
+
An `ActorQueue` can easily enqueue tasks that execute on an actor’s isolated context from a nonisolated context in order:
122
97
```swift
123
98
functestActorQueueOrdering() async {
124
99
actorCounter {
125
-
funcincrement() ->Int {
126
-
count +=1
127
-
return count
100
+
init() {
101
+
// Adopting the execution context in `init` satisfies requirement #2 above.
Copy file name to clipboardExpand all lines: Sources/AsyncQueue/ActorQueue.swift
+79-55Lines changed: 79 additions & 55 deletions
Original file line number
Diff line number
Diff line change
@@ -20,56 +20,54 @@
20
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
// SOFTWARE.
22
22
23
-
/// A queue that executes asynchronous tasks enqueued from a nonisolated context.
23
+
/// A queue that enables enqueing ordered asynchronous tasks from a nonisolated context onto an adopted actor's isolated context.
24
24
/// Tasks are guaranteed to begin executing in the order in which they are enqueued. However, if a task suspends it will allow subsequently enqueued tasks to begin executing.
25
-
/// Asynchronous tasks sent to this queue execute as they would in an `actor` type, allowing for re-entrancy and non-FIFO behavior when an individual task suspends.
25
+
/// This queue exhibits the execution behavior of an actor: tasks sent to this queue can re-enter the queue, and tasks may execute in non-FIFO order when a task suspends.
26
26
///
27
-
/// An `ActorQueue` is used to ensure tasks sent from a nonisolated context to a single `actor`'s isolated context begin execution in order.
28
-
/// Here is an example of how an `ActorQueue` should be utilized within an `actor`:
27
+
/// An `ActorQueue` ensures tasks sent from a nonisolated context to a single actor's isolated context begin execution in order.
28
+
/// Here is an example of how an `ActorQueue` should be utilized within an actor:
29
29
/// ```swift
30
30
/// public actor LogStore {
31
31
///
32
+
/// public init() {
33
+
/// queue.adoptExecutionContext(of: self)
34
+
/// }
35
+
///
32
36
/// nonisolated
33
37
/// public func log(_ message: String) {
34
-
/// queue.async {
35
-
/// await self.append(message)
38
+
/// queue.async { myself in
39
+
/// myself.logs.append(message)
36
40
/// }
37
41
/// }
38
42
///
39
43
/// nonisolated
40
44
/// public func retrieveLogs() async -> [String] {
41
-
/// await queue.await { await self.logs }
42
-
/// }
43
-
///
44
-
/// private func append(_ message: String) {
45
-
/// logs.append(message)
45
+
/// await queue.await { myself in myself.logs }
46
46
/// }
47
47
///
48
-
/// private let queue = ActorQueue()
48
+
/// private let queue = ActorQueue<LogStore>()
49
49
/// private var logs = [String]()
50
50
/// }
51
51
/// ```
52
52
///
53
-
/// - Warning: Execution order is not guaranteed unless the enqueued tasks interact with a single `actor` instance.
54
-
publicfinalclassActorQueue{
53
+
/// - Precondition: The lifecycle of an `ActorQueue` must not exceed that of the adopted actor.
54
+
publicfinalclassActorQueue<ActorType:Actor>{
55
55
56
56
// MARK: Initialization
57
57
58
58
/// Instantiates an actor queue.
59
-
/// - Parameter priority: The baseline priority of the tasks added to the asynchronous queue.
0 commit comments