Skip to content

Commit 3b8bbce

Browse files
committed
Add README and inline documentation for AsyncCoalescingQueue
1 parent 9778867 commit 3b8bbce

2 files changed

Lines changed: 77 additions & 7 deletions

File tree

README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@
66
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
77

88

9-
AsyncSerialQueue is a simple library to provide [serial queue](https://www.avanderlee.com/swift/concurrent-serial-dispatchqueue/)-like capability using [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/). Tasks placed in an AsyncSerialQueue are guaranteed to execute sequentially.
9+
`AsyncSerialQueue` is a library provides some useful patterns using Swift Concurrency:
10+
11+
`AsyncSerialQueue` is a class provides [serial queue](https://www.avanderlee.com/swift/concurrent-serial-dispatchqueue/)-like capability using [Swift Concurrency](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/). Tasks placed in an `AsyncSerialQueue` are guaranteed to execute sequentially.
12+
13+
`AsyncCoalescingQueue` is a companion class that has properties similar to [DispatchSource](https://www.mikeash.com/pyblog/friday-qa-2009-09-11-intro-to-grand-central-dispatch-part-iii-dispatch-sources.html).
1014

1115
AsyncSerialQueue is currently only available on Apple platforms (i.e. not on Linux). This is because of the need for locking and Swift [currently does not have a standard cross-platform locking mechnism](https://forums.swift.org/t/shared-mutable-state-sendable-and-locks/64336).
1216

1317

18+
# `AsyncSerialQueue`
19+
1420
## Example
1521

1622
### Simple Example
@@ -100,3 +106,42 @@ func example() async {
100106
101107
In this case, `apple` will never appear before `2`. And `example()` will not return until `2` is printed.
102108
109+
110+
# `AsyncCoalescingQueue`
111+
112+
## Example
113+
114+
The following code:
115+
116+
```swift
117+
let coalescingQueue = AsyncCoalescingQueue()
118+
119+
coalescingQueue.run {
120+
try? await Task.sleep(for: .seconds(5))
121+
print("Run 1")
122+
}
123+
coalescingQueue.run {
124+
try? await Task.sleep(for: .seconds(5))
125+
print("Run 2")
126+
}
127+
coalescingQueue.run {
128+
try? await Task.sleep(for: .seconds(5))
129+
print("Run 3")
130+
}
131+
coalescingQueue.run {
132+
try? await Task.sleep(for: .seconds(5))
133+
print("Run 4")
134+
}
135+
coalescingQueue.run {
136+
try? await Task.sleep(for: .seconds(5))
137+
print("Run 5")
138+
}
139+
```
140+
Will output the following:
141+
142+
```
143+
Run 1
144+
Run 5
145+
```
146+
147+
And take 10 seconds to complete executing.

Sources/AsyncSerialQueue/AsyncCoalescingQueue.swift

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,42 @@
88
import Foundation
99
import os
1010

11-
/// Provides behavior similar to DispatchSource using Swift Concurrency
11+
/// ``AsyncCoalescingQueue`` provides behavior similar to DispatchSource using Swift Concurrency
12+
/// When the queue is idle, the first task queued is guaranteed to run. Subsequent tasks will only execute when the queue is idle. If more the one task is pending execution, only the last one will execute.
13+
///
14+
/// For example, if executing:
15+
/// ```swift
16+
/// let coalescingQueue = AsyncCoalescingQueue()
17+
/// coalescingQueue.run { await slowFunction(1) }
18+
/// coalescingQueue.run { await slowFunction(2) }
19+
/// coalescingQueue.run { await slowFunction(3) }
20+
/// coalescingQueue.run { await slowFunction(4) }
21+
/// ```
22+
/// This is equivalent to:
23+
/// ```swift
24+
/// await slowFunction(1)
25+
/// await slowFunction(4)
26+
/// ```
1227
public class AsyncCoalescingQueue: @unchecked Sendable {
1328
public let label: String?
1429
private let serialQueue: AsyncSerialQueue
1530

1631
private let taskList = TaskList()
1732
private let priority: TaskPriority?
1833

34+
/// Initialize a new ``AsyncCoalescingQueue`` instance
35+
/// - Parameters:
36+
/// - label: An optional string that can be used to identify the queue
37+
/// - priority: Optional ``TaskPriority`` used when executing tasks
1938
public init(label: String? = nil, priority: TaskPriority? = nil) {
2039
self.label = label
2140
self.priority = priority
2241
self.serialQueue = AsyncSerialQueue(label: label, priority: priority)
2342
}
24-
43+
2544
/// Attempt to execute a given block.
26-
/// Will not attempt to execute more than 2 in-flight blocks.
45+
/// The block will not be executed if a new block is queued before it runs.
46+
/// Subsequent blocks are guaranteed to not run at the same time.
2747
/// - Parameter block: block to execute
2848
public func run(_ block: @escaping () async -> Void) {
2949
self.serialQueue.async {
@@ -34,6 +54,7 @@ public class AsyncCoalescingQueue: @unchecked Sendable {
3454
}
3555
}
3656

57+
/// Wait for all pending blocks to execute.
3758
public func wait() async {
3859
await self.serialQueue.sync {
3960
while await self.taskList.isEmpty == false {
@@ -42,14 +63,18 @@ public class AsyncCoalescingQueue: @unchecked Sendable {
4263
}
4364
}
4465
}
66+
}
67+
68+
// MARK: - Private
69+
fileprivate extension AsyncCoalescingQueue {
4570

46-
private func triggerProcessNextTask() {
71+
func triggerProcessNextTask() {
4772
Task(priority: self.priority) {
4873
await self.processNextTask()
4974
}
5075
}
5176

52-
private func processNextTask() async {
77+
func processNextTask() async {
5378
if await self.taskList.isAnyTaskRunning {
5479
return
5580
}
@@ -62,7 +87,7 @@ public class AsyncCoalescingQueue: @unchecked Sendable {
6287
case .running:
6388
break
6489
case .waiting:
65-
Task {
90+
Task(priority: self.priority) {
6691
await task.run()
6792

6893
await self.processNextTask()

0 commit comments

Comments
 (0)