@@ -33,53 +33,93 @@ extension NSLock {
33
33
}
34
34
}
35
35
36
- /// A serial queue that allows the execution of asyncronous blocks of code.
36
+ /// A queue that allows the execution of asyncronous blocks of code.
37
37
public final class AsyncQueue {
38
- /// Lock guarding `lastTask`.
39
- private let lastTaskLock = NSLock ( )
38
+ public enum QueueKind {
39
+ /// A queue that allows concurrent execution of tasks.
40
+ case concurrent
40
41
41
- /// The last scheduled task if it hasn't finished yet.
42
- ///
43
- /// Any newly scheduled tasks need to await this task to ensure that tasks are
44
- /// executed syncronously.
45
- ///
46
- /// `id` is a unique value to identify the task. This allows us to set `lastTask`
47
- /// to `nil` if the queue runs empty.
48
- private var lastTask : ( task: AnyTask , id: UUID ) ?
42
+ /// A queue that executes one task after the other.
43
+ case serial
44
+ }
45
+
46
+ private struct PendingTask {
47
+ /// The task that is pending.
48
+ let task : any AnyTask
49
+
50
+ /// Whether the task needs to finish executing befoer any other task can
51
+ /// start in executing in the queue.
52
+ let isBarrier : Bool
49
53
50
- public init ( ) {
51
- self . lastTaskLock. name = " AsyncQueue.lastTaskLock "
54
+ /// A unique value used to identify the task. This allows tasks to get
55
+ /// removed from `pendingTasks` again after they finished executing.
56
+ let id : UUID
57
+ }
58
+
59
+ /// Whether the queue allows concurrent execution of tasks.
60
+ private let kind : QueueKind
61
+
62
+ /// Lock guarding `pendingTasks`.
63
+ private let pendingTasksLock = NSLock ( )
64
+
65
+ /// Pending tasks that have not finished execution yet.
66
+ private var pendingTasks = [ PendingTask] ( )
67
+
68
+ public init ( _ kind: QueueKind ) {
69
+ self . kind = kind
70
+ self . pendingTasksLock. name = " AsyncQueue "
52
71
}
53
72
54
73
/// Schedule a new closure to be executed on the queue.
55
74
///
56
- /// All previously added tasks are guaranteed to finished executing before
57
- /// this closure gets executed.
75
+ /// If this is a serial queue, all previously added tasks are guaranteed to
76
+ /// finished executing before this closure gets executed.
77
+ ///
78
+ /// If this is a barrier, all previously scheduled tasks are guaranteed to
79
+ /// finish execution before the barrier is executed and all tasks that are
80
+ /// added later will wait until the barrier finishes execution.
58
81
@discardableResult
59
82
public func async < Success: Sendable > (
60
83
priority: TaskPriority ? = nil ,
84
+ barrier isBarrier: Bool = false ,
61
85
@_inheritActorContext operation: @escaping @Sendable ( ) async -> Success
62
86
) -> Task < Success , Never > {
63
87
let id = UUID ( )
64
88
65
- return lastTaskLock. withLock {
66
- let task = Task < Success , Never > ( priority: priority) { [ previousLastTask = lastTask] in
67
- await previousLastTask? . task. waitForCompletion ( )
68
-
69
- defer {
70
- lastTaskLock. withLock {
71
- // If we haven't queued a new task since enquing this one, we can clear
72
- // last task.
73
- if self . lastTask? . id == id {
74
- self . lastTask = nil
75
- }
76
- }
89
+ return pendingTasksLock. withLock {
90
+ // Build the list of tasks that need to finishe exeuction before this one
91
+ // can be executed
92
+ let dependencies : [ PendingTask ]
93
+ switch ( kind, isBarrier: isBarrier) {
94
+ case ( . concurrent, isBarrier: true ) :
95
+ // Wait for all tasks after the last barrier.
96
+ let lastBarrierIndex = pendingTasks. lastIndex ( where: { $0. isBarrier } ) ?? pendingTasks. startIndex
97
+ dependencies = Array ( pendingTasks [ lastBarrierIndex... ] )
98
+ case ( . concurrent, isBarrier: false ) :
99
+ // If there is a barrier, wait for it.
100
+ dependencies = [ pendingTasks. last ( where: { $0. isBarrier } ) ] . compactMap { $0 }
101
+ case ( . serial, _) :
102
+ // We are in a serial queue. The last pending task must finish for this one to start.
103
+ dependencies = [ pendingTasks . last ] . compactMap { $0 }
104
+ }
105
+
106
+
107
+ // Schedule the task.
108
+ let task = Task {
109
+ for dependency in dependencies {
110
+ await dependency. task. waitForCompletion ( )
111
+ }
112
+
113
+ let result = await operation ( )
114
+
115
+ pendingTasksLock. withLock {
116
+ pendingTasks. removeAll ( where: { $0. id == id } )
77
117
}
78
118
79
- return await operation ( )
119
+ return result
80
120
}
81
121
82
- lastTask = ( task, id )
122
+ pendingTasks . append ( PendingTask ( task: task , isBarrier : isBarrier , id : id ) )
83
123
84
124
return task
85
125
}
0 commit comments