我有播放和暂停按钮。当我按下播放按钮时,我想在循环内播放异步对话。我使用派发组进行异步方法在for循环内的等待。但我无法实现停顿。
startStopButton.rx.tap.bind {
if self.isPaused {
self.isPaused = false
dispatchGroup.suspend()
dispatchQueue.suspend()
} else {
self.isPaused = true
self.dispatchQueue.async {
for i in 0..<self.textBlocks.count {
self.dispatchGroup.enter()
self.startTalking(string: self.textBlocks[i]) { isFinished in
self.dispatchGroup.leave()
}
self.dispatchGroup.wait()
}
}
}
}.disposed(by: disposeBag)
我尝试处理操作队列,但仍然无法正常工作。它仍然在继续聊天。
startStopButton.rx.tap.bind {
if self.isPaused {
self.isPaused = false
self.talkingQueue.isSuspended = true
self.talkingQueue.cancelAllOperations()
} else {
self.isPaused = true
self.talkingQueue.addOperation {
for i in 0..<self.textBlocks.count {
self.dispatchGroup.enter()
self.startTalking(string: self.textBlocks[i]) { isFinished in
self.dispatchGroup.leave()
}
self.dispatchGroup.wait()
}
}
}
}.disposed(by: disposeBag)
有什么建议吗?
答案 0 :(得分:3)
一些观察结果:
暂停群组不会执行任何操作。您暂停队列,而不是组。
挂起队列可以阻止新项目在该队列上启动,但不会挂起该队列上已在运行的任何内容。因此,如果您将所有textBlock
调用都添加到一个已分派的工作项中,那么一旦开始,它就不会挂起。
因此,与其将所有这些文本块作为一个单独的任务分配到队列中,不如将它们单独提交(当然,假设您的队列是串行的)。举例来说,假设您有一个DispatchQueue
:
let queue = DispatchQueue(label: "...")
然后,要将任务排队,请将async
调用放入 for
循环中,因此每个文本块都是队列中的单独项目:
for textBlock in textBlocks {
queue.async { [weak self] in
guard let self = self else { return }
let semaphore = DispatchSemaphore(value: 0)
self.startTalking(string: textBlock) {
semaphore.signal()
}
semaphore.wait()
}
}
仅供参考,尽管调度组可以工作,但信号量(伟大的是将单个signal
与wait
进行协调)可能是一个更合理的选择,而不是组(用于协调组)分派的任务)。
无论如何,当您暂停该队列时,该队列将阻止启动任何已排队的对象(但将完成当前的textBlock
)。
或者您可以使用异步Operation
,例如,创建队列:
let queue: OperationQueue = {
let queue = OperationQueue()
queue.name = "..."
queue.maxConcurrentOperationCount = 1
return queue
}()
然后,再次将每个说出的单词排入队列,在该队列上分别进行单独的操作:
for textBlock in textBlocks {
queue.addOperation(TalkingOperation(string: textBlock))
}
那当然假设您将谈话例程封装在一个操作中,例如:
class TalkingOperation: AsynchronousOperation {
let string: String
init(string: String) {
self.string = string
}
override func main() {
startTalking(string: string) {
self.finish()
}
}
func startTalking(string: String, completion: @escaping () -> Void) { ... }
}
我更喜欢这种方法,因为
TalkingOperation
中;和顺便说一下,这是AsynchronousOperation
的子类,它从TalkingOperation
类中抽象出了异步操作的复杂性。有很多方法可以做到这一点,但是这里是one random implementation。 FWIW的想法是,您定义一个AsynchronousOperation
子类,该子类执行documentation中概述的异步操作所需的所有KVO,然后您可以享受操作队列的好处,而不必进行每个异步操作子类太复杂了。
关于它的价值,如果您不需要暂停,但很乐意取消,另一种方法是将整个for
循环作为单个工作项或操作进行分派,但请检查一下查看该操作是否已在for
循环内取消:
因此,定义一些属性:
let queue = DispatchQueue(label: "...")
var item: DispatchWorkItem?
然后您可以启动任务:
item = DispatchWorkItem { [weak self] in
guard let textBlocks = self?.textBlocks else { return }
for textBlock in textBlocks where self?.item?.isCancelled == false {
let semaphore = DispatchSemaphore(value: 0)
self?.startTalking(string: textBlock) {
semaphore.signal()
}
semaphore.wait()
}
self?.item = nil
}
queue.async(execute: item!)
然后,当您想要停止它时,只需致电item?.cancel()
。您也可以使用非异步Operation
来执行相同的模式。