如何暂停循环内的调度队列?



我有播放和暂停按钮。当我按下播放按钮时,我想在循环内播放异步通话。我使用调度组进行异步方法在循环内等待。但我无法停顿。

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)

有什么建议吗?

一些观察:

  1. 暂停组不会执行任何操作。您暂停队列,而不是组。

  2. 挂起队列会阻止新项目在该队列上启动,但不会挂起该队列上已运行的任何内容。因此,如果您已将所有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()
    }
    }
    

    仅供参考,虽然调度组工作,但信号量(非常适合协调单个signalwait(在这里可能是一个更合乎逻辑的选择,而不是一个组(用于协调调度任务组(。

    无论如何,当您挂起该队列时,该队列将阻止启动任何排队的内容(但将完成当前textBlock(。

  3. 或者您可以使用异步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类中抽象出来。有很多方法可以做到这一点,但这里有一个随机实现。FWIW,这个想法是你定义一个AsynchronousOperation子类,它执行文档中概述的异步操作所需的所有 KVO,然后你可以享受操作队列的好处,而不会使每个异步操作子类过于复杂。

  4. 值得一提的是,如果您不需要挂起,但很乐意取消,另一种方法是将整个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执行相同的模式。