Skip to content

Commit 36da1d8

Browse files
committed
Hop queues only once
1 parent 636a72b commit 36da1d8

File tree

1 file changed

+35
-27
lines changed

1 file changed

+35
-27
lines changed

Sources/AsyncQueue/ActorQueue.swift

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,31 +57,7 @@ public final class ActorQueue<ActorType: Actor>: @unchecked Sendable {
5757

5858
/// Instantiates an actor queue.
5959
public init() {
60-
let (taskStream, taskStreamContinuation) = AsyncStream<ActorTask>.makeStream()
61-
self.taskStreamContinuation = taskStreamContinuation
62-
63-
func beginExecuting(
64-
_ operation: sending @escaping (isolated ActorType) async -> Void,
65-
in context: isolated ActorType
66-
) {
67-
// In Swift 6, a `Task` enqueued from an actor begins executing immediately on that global actor.
68-
// Since we're running on our actor's context already, we can just dispatch a Task to get first-enqueued-first-start task execution.
69-
Task {
70-
await operation(context)
71-
}
72-
}
73-
74-
Task {
75-
// In an ideal world, we would isolate this `for await` loop to the `ActorType`.
76-
// However, there's no good way to do that just yet.
77-
for await actorTask in taskStream {
78-
// Await switching to the ActorType context.
79-
await beginExecuting(
80-
actorTask.task,
81-
in: actorTask.executionContext
82-
)
83-
}
84-
}
60+
(taskStream, taskStreamContinuation) = AsyncStream<ActorTask>.makeStream()
8561
}
8662

8763
deinit {
@@ -95,10 +71,30 @@ public final class ActorQueue<ActorType: Actor>: @unchecked Sendable {
9571
/// **Must be called prior to enqueuing any work on the receiver.**
9672
///
9773
/// - Parameter actor: The actor on which the queue's task will execute. This parameter is not retained by the receiver.
98-
/// - Warning: Calling this method more than once will result in an assertion failure.
74+
/// - Precondition: Calling this method more than once will result in a precondition failure.
9975
public func adoptExecutionContext(of actor: ActorType) {
100-
assert(weakExecutionContext == nil) // Adopting multiple executionContexts on the same queue is API abuse.
76+
precondition(weakExecutionContext == nil) // Adopting multiple executionContexts on the same queue is API abuse.
10177
weakExecutionContext = actor
78+
79+
actor.execute { [taskStream] _ in
80+
func beginExecuting(
81+
_ operation: sending @escaping (isolated ActorType) async -> Void,
82+
in context: isolated ActorType
83+
) {
84+
// In Swift 6, a `Task` enqueued from an actor begins executing immediately on that actor.
85+
// Since we're running on our actor's context due to the isolated parmater, we can just dispatch a Task to get first-enqueued-first-start task execution.
86+
Task {
87+
await operation(context)
88+
}
89+
}
90+
91+
for await actorTask in taskStream {
92+
await beginExecuting(
93+
actorTask.task,
94+
in: actorTask.executionContext
95+
)
96+
}
97+
}
10298
}
10399

104100
/// Schedules an asynchronous task for execution and immediately returns.
@@ -140,6 +136,7 @@ public final class ActorQueue<ActorType: Actor>: @unchecked Sendable {
140136

141137
// MARK: Private
142138

139+
private let taskStream: AsyncStream<ActorQueue<ActorType>.ActorTask>
143140
private let taskStreamContinuation: AsyncStream<ActorTask>.Continuation
144141

145142
/// The actor on whose isolated context our tasks run, force-unwrapped.
@@ -163,3 +160,14 @@ public final class ActorQueue<ActorType: Actor>: @unchecked Sendable {
163160
let task: @Sendable (isolated ActorType) async -> Void
164161
}
165162
}
163+
164+
extension Actor {
165+
nonisolated
166+
func execute(
167+
_ task: @escaping @Sendable (isolated Self?) async -> Void
168+
) {
169+
Task {
170+
await task(nil)
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)