如何在for循环中暂停调度队列?

问题描述 投票:1回答:1

我有播放和暂停按钮。当我按下播放按钮时,我想在循环内播放异步对话。我使用派发组进行异步方法在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)

有什么建议吗?

ios swift grand-central-dispatch nsoperationqueue
1个回答
1
投票

一些观察:

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

  2. 挂起队列可阻止新项目在该队列上启动,但不会挂起该队列上已在运行的任何内容。因此,如果您将所有textBlock呼叫都添加到一个已分派的工作项中,那么一旦开始,它就不会挂起。

    因此,与其将所有这些文本块作为一个单独的任务分配到队列中,不如将它们单独提交(当然,假设您的队列是串行的)。例如,假设您有一个DispatchQueue

    let queue = DispatchQueue(label: "...")
    

    然后,将任务排队,将async调用inside放入for循环,因此每个文本块都是队列中的单独项目:

    for textBlock in textBlocks {
        queue.async { [weak self] in
            guard let self = self else { return }
    
            let group = DispatchGroup()
    
            group.enter()
            self.startTalking(string: textBlock) {
                group.leave()
            }
            group.wait()
        }
    }
    

    然后,当您挂起该队列(而不是组)时,该队列将阻止启动任何排队的队列(但将完成当前的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类中抽象出了异步操作的复杂性。有很多方法可以做到这一点,但是这里是one random implementation。 FWIW的想法是,您定义一个Operation子类,该子类执行documentation中概述的异步操作所需的所有KVO,然后您可以享受操作队列的好处。

  4. 对于它的价值,如果您不需要暂停,但很乐意取消,另一种方法是将整个for循环作为单个工作项或操作进行分派,但是请检查是否for循环内的操作已被取消:

    因此,定义一些属性:

    let queue = DispatchQueue(label: "...")
    var item: DispatchWorkItem?
    

    然后您可以开始任务:

    item = DispatchWorkItem { [weak self] in
        guard let textBlocks = self?.textBlocks else { return }
    
        let group = DispatchGroup()
        for textBlock in textBlocks where self?.item?.isCancelled == false {
            group.enter()
            self?.startTalking(string: textBlock) {
                group.leave()
            }
            group.wait()
        }
        self?.item = nil
    }
    
    queue.async(execute: item!)
    

    然后,当您要停止它时,只需调用item?.cancel()。您也可以使用非异步Operation来执行相同的模式。

© www.soinside.com 2019 - 2024. All rights reserved.