【发布时间】:2021-12-31 08:48:17
【问题描述】:
我目前正在迁移我的应用程序以使用 Swift 中的并发模型。我想序列化任务以确保它们一个接一个地执行(没有并行性)。在我的用例中,我想收听 NotificationCenter 发布的通知,并在每次发布新通知时执行一个任务。但我想确保没有以前的任务正在运行。这相当于使用 maxConcurrentOperationCount = 1 的 OperationQueue。
例如,我在我的应用程序中使用 CloudKit 和 Core Data,并使用持久历史跟踪来确定商店中发生了哪些变化。 在这个Synchronizing a Local Store to the Cloud 示例代码中,Apple 使用操作队列来处理历史处理任务(在 CoreDataStack 中)。此 OperationQueue 的最大操作数设置为 1。
private lazy var historyQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
当收到一个 Core Data 通知时,一个新任务被添加到这个串行操作队列中。因此,如果收到很多通知,它们都会以串行方式一个接一个地执行。
@objc
func storeRemoteChange(_ notification: Notification) {
// Process persistent history to merge changes from other coordinators.
historyQueue.addOperation {
self.processPersistentHistory()
}
}
在这个Loading and Displaying a Large Data Feed 示例代码中,Apple 使用任务来处理历史更改(在 QuakesProvider 中)。
// Observe Core Data remote change notifications on the queue where the changes were made.
notificationToken = NotificationCenter.default.addObserver(forName: .NSPersistentStoreRemoteChange, object: nil, queue: nil) { note in
Task {
await self.fetchPersistentHistory()
}
}
我觉得第二个项目有问题,因为任务可以按任何顺序发生,不一定按顺序发生(与第一个项目中 OperationQueue 作为 maxConcurrentOperationCount = 1 的情况相反)。
我们是否应该在某处使用演员来确保方法被串行调用?
我想过这样的实现,但我还不太适应:
actor PersistenceStoreListener {
let historyTokenManager: PersistenceHistoryTokenManager = .init()
private let persistentContainer: NSPersistentContainer
init(persistentContainer: NSPersistentContainer) {
self.persistentContainer = persistentContainer
}
func processRemoteStoreChange() async {
print("\(#function) called on \(Date.now.formatted(date: .abbreviated, time: .standard)).")
}
}
收到新通知时将调用 processRemoteStoreChange 方法的位置(AsyncSequence):
notificationListenerTask = Task {
let notifications = NotificationCenter.default.notifications(named: .NSPersistentStoreRemoteChange, object: container.persistentStoreCoordinator)
for await _ in notifications {
print("notificationListenerTask called on \(Date.now.formatted(date: .abbreviated, time: .standard)).")
await self.storeListener?.processRemoteStoreChange()
}
}
【问题讨论】:
-
在 Swift Concurrency 中,顺序执行是正常情况,除非您使用
async let或分离任务。在一个任务组中,迭代可以同时执行,但结果是按顺序等待的。在您的情况下,AsyncSequence是正确的方法。 -
@vadian 所以你的意思是 Apple 使用 Task (developer.apple.com/documentation/coredata/…) 的第二个项目中的代码是以串行方式执行任务?不存在稍后安排的任务在另一个已安排的任务之前执行的风险?
-
是的,正如名称
AsyncSequence所暗示的那样,它是一个序列,并且序列顺序执行。但请随意尝试。 -
Hum Apple 没有在他们的代码中使用 AsyncSequence(这是我的建议)。但是即使是,也意味着通知是按顺序接收的,但是当我在Task中调用异步方法时,它们也会按顺序执行吗?我对不同任务的实际处理方式有点困惑。
-
@matt 你是对的,我很抱歉。 TaskGroup 按完成顺序返回项目。但是 AsyncSequence 按顺序返回项目。
标签: swift core-data grand-central-dispatch nsoperationqueue swift-concurrency