swift-async-queue
一个队列库,允许将有序任务从同步环境发送到异步环境。
任务排序与 Swift 并发
在 Swift Concurrency 中,从同步环境向异步环境发送的任务通常是未排序的。考虑以下测试
@MainActor
func testMainActorTaskOrdering() async {
actor Counter {
func incrementAndAssertCountEquals(_ expectedCount: Int) {
count += 1
let incrementedCount = count
XCTAssertEqual(incrementedCount, expectedCount) // often fails
}
private var count = 0
}
let counter = Counter()
var tasks = [Task<Void, Never>]()
for iteration in 1...100 {
tasks.append(Task {
await counter.incrementAndAssertCountEquals(iteration)
})
}
// Wait for all enqueued tasks to finish.
for task in tasks {
_ = await task.value
}
}
尽管产生的 Task
继承了序列 @MainActor
执行上下文,但计划中的异步工作排序不可保证。
虽然 actors 很擅长序列化任务,但在标准的 Swift 库中没有简单的方法可以将其从同步环境发送给它们。
以 FIFO 顺序执行异步任务
使用一个 FIFOQueue
以 FIFO 顺序执行来自非隔离上下文队列的异步任务。发送到这些队列的任务将保证按它们入队的顺序开始和结束执行。FIFOQueue
与 DispatchQueue
的执行方式类似:入队任务原子执行,如果在 FIFOQueue
上执行的任务等待其在执行的任务队列的结果,程序将发生死锁。
FIFOQueue
可以轻松地按 FIFO 顺序从非隔离上下文执行异步任务
func testFIFOQueueOrdering() async {
actor Counter {
nonisolated
func incrementAndAssertCountEquals(_ expectedCount: Int) {
queue.enqueue {
await self.increment()
let incrementedCount = await self.count
XCTAssertEqual(incrementedCount, expectedCount) // always succeeds
}
}
func flushQueue() async {
await queue.enqueueAndWait { }
}
func increment() {
count += 1
}
var count = 0
private let queue = FIFOQueue()
}
let counter = Counter()
for iteration in 1...100 {
counter.incrementAndAssertCountEquals(iteration)
}
// Wait for all enqueued tasks to finish.
await counter.flushQueue()
}
FIFO 执行的关键缺点是队列必须等待所有之前入队的工作(包括挂起的工作)完成后才能开始新工作。如果您希望前一个任务挂起时开始新工作,请使用 ActorQueue
。
从非隔离上下文向演员发送有序的异步任务
使用 ActorQueue
将有序的异步任务发送到 actor
的隔离上下文,源自非隔离或同步上下文。发送到 actor 队列的任务将保证按它们入队的顺序开始执行。然而,与 FIFOQueue
不同,执行顺序只在入队任务中的第一个挂起点(suspension point)之前得到保证。ActorQueue
在其采用的 actor 的隔离上下文中执行任务,因此 ActorQueue
任务执行的属性与 actor 代码执行相同:挂起点之间的代码原子执行,并且可以向单个 ActorQueue
发送任务而不会死锁。
ActorQueue
的一个实例是为单个 actor
实例设计的:发送到 ActorQueue
的任务使用队列采用的 actor
的隔离上下文来序列化任务。因此,在处理 ActorQueue
时必须满足一些要求
- 任何
ActorQueue
的生命周期不应超过其actor
的生命周期。强烈建议将ActorQueue
作为采用的actor
上的private let
常量。在其采用的actor
被解除分配后,向ActorQueue
实例入队任务会导致崩溃。 - 使用
ActorQueue
的actor
应在actor
的init
中将队列的采用的执行上下文设置为self
。在向ActorQueue
入队工作之前没有设置采用的执行上下文会导致崩溃。
ActorQueue
可以轻松地将任务有序地入队到 actor 的隔离上下文中,从非隔离上下文执行
func testActorQueueOrdering() async {
actor Counter {
init() {
// Adopting the execution context in `init` satisfies requirement #2 above.
queue.adoptExecutionContext(of: self)
}
nonisolated
func incrementAndAssertCountEquals(_ expectedCount: Int) {
queue.enqueue { myself in
myself.count += 1
XCTAssertEqual(expectedCount, myself.count) // always succeeds
}
}
func flushQueue() async {
await queue.enqueueAndWait { _ in }
}
private var count = 0
// Making the queue a private let constant satisfies requirement #1 above.
private let queue = ActorQueue<Counter>()
}
let counter = Counter()
for iteration in 1...100 {
counter.incrementAndAssertCountEquals(iteration)
}
// Wait for all enqueued tasks to finish.
await counter.flushQueue()
}
@MainActor
将有序异步任务从非隔离环境发送到 使用 MainActorQueue
将有序异步任务从非隔离或同步环境发送到 @MainActor
的隔离上下文。发送到这种队列类型的任务将保证按入队顺序开始执行。与 ActorQueue
一样,执行顺序保证仅限于入队任务中的第一个 挂起点。在它采用的角色的隔离上下文中执行 MainActorQueue
任务,因此 MainActorQueue
任务执行具有与 @MainActor
代码执行相同的属性:挂起点之间的代码是原子执行的,并发送到单个 MainActorQueue
的任务可以等待队列结果而不死锁。
MainActorQueue
可以轻松地以先入先出的顺序在非隔离环境中执行异步任务
@MainActor
func testMainActorQueueOrdering() async {
@MainActor
final class Counter {
nonisolated
func incrementAndAssertCountEquals(_ expectedCount: Int) {
MainActorQueue.shared.enqueue {
self.increment()
let incrementedCount = self.count
XCTAssertEqual(incrementedCount, expectedCount) // always succeeds
}
}
func flushQueue() async {
await MainActorQueue.shared.enqueueAndWait { }
}
func increment() {
count += 1
}
var count = 0
}
let counter = Counter()
for iteration in 1...100 {
counter.incrementAndAssertCountEquals(iteration)
}
// Wait for all enqueued tasks to finish.
await counter.flushQueue()
}
要求
- Xcode 15.0 或更高版本。
- iOS 13 或更高版本。
- tvOS 13 或更高版本。
- watchOS 6 或更高版本。
- macOS 10.15 或更高版本。
- Swift 5.9 或更高版本。
安装
Swift 包管理器
要在您的项目中使用Swift Package Manager安装swift-async-queue,可以将以下行添加到您的Package.swift
文件中。
dependencies: [
.package(url: "https://github.com/dfed/swift-async-queue", from: "0.5.0"),
]
CocoaPods
使用CocoaPods在您的项目中安装swift-async-queue,请将以下内容添加到您的Podfile
文件中。
platform :ios, '13.0'
pod 'AsyncQueue', '~> 0.5.0'
贡献
我很高兴您对swift-async-queue感兴趣,并期待看到您如何发展它。在提交拉取请求之前,请先阅读贡献指南。
感谢,祝您排队顺利!
开发
双击仓库根目录下的Package.swift
文件以在Xcode中打开项目。