A lightweight Swift concurrency utility to ensure closures are executed serially, one at a time.
AsyncConveyor
is a concurrency control primitive that guarantees only one async block runs at a time. When multiple tasks invoke run(_:)
, they are automatically queued and executed in FIFO order. Ideal for controlling access to critical resources like databases, files, or network sessions.
- ๐ Serial execution of async closures
- โ Supports structured concurrency and cancellation handling
- ๐งต Lock-free internal implementation using
ManagedCriticalState
- ๐งช Well-suited for unit testing and task coordination scenarios
- ๐ง No external dependencies except Swift standard concurrency APIs
- Swift 5.9+
- Concurrency enabled (Swift Concurrency runtime)
- Supported Platforms:
- iOS 13+
- macOS 10.15+
- watchOS 6+
- tvOS 13+
.package(url: "https://github.com/0xfeedface1993/swift-async-conveyor.git", from: "1.0.0")
Then add "AsyncConveyor" to your target dependencies.
import AsyncConveyor
let conveyor = AsyncConveyor()
Task {
try await conveyor.run {
print("Task A starting")
try await Task.sleep(nanoseconds: 1_000_000_000)
print("Task A finished")
}
}
Task {
try await conveyor.run {
print("Task B starting")
try await Task.sleep(nanoseconds: 500_000_000)
print("Task B finished")
}
}
Task A starting
Task A finished
Task B starting
Task B finished
Note: Even though Task B starts earlier in wall time, it waits for Task A to complete before executing.
If a task is cancelled while waiting or running, AsyncConveyor ensures:
- The task is properly removed from the internal queue
- The next task resumes correctly
- No memory leaks or deadlocks
- Serializing file reads/writes
- Managing access to a database or cache
- Enforcing order of operations in state machines
- Coordinating request pipelines
- Preventing overlapping animations or transitions
- Each .run { } block is awaited and can be tested deterministically.
- Consider injecting AsyncConveyor as a dependency when testing logic that depends on ordering.
AsyncConveyor follows a minimal locking, actor-free design by using ManagedCriticalState to efficiently manage internal state. It behaves similarly to a serial DispatchQueue, but with native async/await and structured cancellation.
Contributions and feedback are welcome! Please open issues or PRs.