【问题标题】:How to schedule a NSOperation to the head of queue and let all other operations waiting for it? [closed]如何将 NSOperation 调度到队列的头部并让所有其他操作等待它? [关闭]
【发布时间】:2021-09-24 06:08:08
【问题描述】:

我有一个并发的NSOperationQueue。对于特定的NSOperation,如果失败了,我想立即以最高优先级重试这个操作,并暂停所有其他操作,直到成功为止。

我可以考虑安排一个具有更高优先级的操作,但是我怎样才能让所有其他操作以一种有效的方式等待这个操作呢?更改所有剩余的操作依赖项似乎太耗时了。

【问题讨论】:

  • 您可以查看docs 了解如何解决它。
  • @Rob 谢谢 Rob。我的队列是并发的。如何在另一个队列上运行重试操作时暂停并发队列?
  • 顺便说一句,您引用NSOperationQueueNSOperation。在 Swift 中,您应该使用 OperationQueueOperation,不带 NS 前缀。

标签: ios swift nsoperationqueue nsoperation


【解决方案1】:

有几种方法:

  1. 一个简单的方法可以打破僵局,就是让可能需要多次尝试的任务在重试完成后才能完成(即将重试登录并入操作本身)。然后用屏障调度第一个任务,调度后续任务,这样后续任务将无法运行,直到第一个任务完成(包括所有重试)。

  2. 或者,如果您想让重试任务独立操作,但不想使用依赖项,您可以将后续任务添加到单独的挂起队列中:

    let taskQueue = OperationQueue()
    taskQueue.maxConcurrentOperationCount = 4
    taskQueue.isSuspended = true
    
    for i in 0 ..< 20 {
        taskQueue.addOperation {
            ...
        }
    }
    

    然后,将可能需要重试的任务添加到另一个队列(即显然未挂起的队列):

    func attempt(_ count: Int = 0) {
        retryQueue.addOperation {
            ...
    
            if isSuccessful {
                taskQueue.isSuspended = false
            } else {
                attempt(count + 1)
            }
    
            ...
        }
    }
    

    当您这样做时,第一个操作将在满足必要条件时取消暂停任务队列:

  3. 为了完整起见,另一种选择是将Operation 子类化并使isReady 逻辑不仅返回其super 实现,而且还观察一些属性。例如

    class WaitingOperation: Operation {
        @objc dynamic var canStart = false
    
        var object: NSObject
        var observer: NSKeyValueObservation?
    
        let taskId: Int
    
        override var isReady: Bool { super.isReady && canStart }
    
        init<T>(object: T, canStartTasksKeyPath keyPath: KeyPath<T, Bool>, taskId: Int) where T: NSObject {
            self.object = object
            self.taskId = taskId
            super.init()
            observer = object.observe(keyPath, options: [.initial, .new]) { [weak self] _, changes in
                if let newValue = changes.newValue {
                    self?.canStart = newValue
                }
            }
        }
    
        override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
            var set = super.keyPathsForValuesAffectingValue(forKey: key)
    
            if key == #keyPath(isReady) {
                set.insert(#keyPath(canStart))
            }
    
            return set
        }
    
        override func main() {
            ...
        }
    }
    

    然后

    @objc dynamic var canStartTasks = false
    
    func begin() {
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 4
    
        for i in 0 ..< 20 {
            queue.addOperation(WaitingOperation(object: self, canStartTasksKeyPath: \.canStartTasks, taskId: i))
        }
    
        let start = CACurrentMediaTime()
        attempt()
    
        func attempt(_ count: Int = 0) {
            queue.addOperation { [self] in
                ...
    
                if notSuccessful {
                    attempt(count + 1)
                } else {
                    canStartTasks = true
                }
    
                ...
            }
        }
    }
    

【讨论】:

  • 谢谢罗伯!这真的很有帮助。关于第一种方法的一个问题。由于Apple已弃用operations,当我的操作意识到需要重试时,是否有可靠的方法来添加依赖项?
  • 还有对于屏障方法,因为OperationQueue只有addBarrierBlock,这会将操作添加到最后,我猜在需要重试的操作之后是否已经有后续操作,我们没有办法添加障碍物吧?
  • 是的,虽然我提倡使用 operations 进行迭代,更新依赖项,正如您指出的那样,这将被弃用,因此我已将其从上面的建议列表中删除。跨度>
  • @Patroclus - 如果第一个操作在重试用尽(或者,显然,如果它成功)之前根本没有完成,则屏障方法有效。如果您计划稍后添加单独的重试操作,则不能使用屏障方法。但是如果重试过程嵌入到第一个操作中,它就可以完美地工作。
  • 谢谢罗伯。这是有道理的。
猜你喜欢
  • 1970-01-01
  • 2012-11-25
  • 1970-01-01
  • 2017-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多