操作变为isFinished = YES,而不是由它所在的队列启动

概观

  • 有一个异步操作子类
  • 将此操作添加到队列中。
  • 我在开始之前取消了这个操作。

运行时错误/警告:

SomeOperation went isFinished=YES without being started by the queue it is in

题:

  1. 这是可以忽略的东西还是严肃的东西?
  2. 怎么解决这个?
  3. 最终提供的变通方法/解决方案是否有效?

码:

 public class SomeOperation : AsyncOperation { //MARK: Start public override func start() { isExecuting = true guard !isCancelled else { markAsCompleted() //isExecuting = false, isFinished = true return } doSomethingAsynchronously { [weak self] in self?.markAsCompleted() //isExecuting = false, isFinished = true } } //MARK: Cancel public override func cancel() { super.cancel() markAsCompleted() //isExecuting = false, isFinished = true } } 

添加到队列并取消:

 //someOperation is a property in a class if let someOperation = someOperation { queue.addOperation(someOperation) } //Based on some condition cancelling it someOperation?.cancel() 

这是有效的解决方案吗?

 public override func cancel() { isExecuting = true //Just in case the operation was cancelled before starting super.cancel() markAsCompleted() } 

注意:

  • markAsCompleted sets isExecuting = falseisFinished = true
  • isExecutingisFinished是同步KVO属性

关键问题是当操作不是isExecuting时,你的markAsCompleted正在触发isFinished 。 我建议你只修复markAsCompleted ,只有当isExecuting为真时才这样做。 这减少了子类执行任何复杂状态测试的负担,以确定它们是否需要转换为isFinished

话虽如此,我在编写可取消的异步操作时会看到三种基本模式:

  1. 如果我正在处理一些模式,其中取消任务将阻止它将执行操作转换为isFinished状态。

    在这种情况下,我必须手动cancel执行完成执行操作。 例如:

     class FiveSecondOperation: AsynchronousOperation { var block: DispatchWorkItem? override func main() { block = DispatchWorkItem { [weak self] in self?.finish() self?.block = nil } DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: block!) } override func cancel() { super.cancel() if isExecuting { block?.cancel() finish() } } } 

    专注于cancel实现,因为如果我取消DispatchWorkItem它将无法完成操作,因此我需要确保cancel将显式完成操作本身。

  2. 有时,当您取消某个异步任务时,它会自动为您调用其完成处理程序,在这种情况下, cancel除了取消该任务并调用super之外不需要执行任何操作。 例如:

     class GetOperation: AsynchronousOperation { var url: URL weak var task: URLSessionTask? init(url: URL) { self.url = url super.init() } override func main() { let task = URLSession.shared.dataTask(with: url) { data, _, error in defer { self.finish() } // make sure to finish the operation // process `data` & `error` here } task.resume() self.task = task } override func cancel() { super.cancel() task?.cancel() } } 

    再次,关注cancel ,在这种情况下,我们不触及“完成”状态,但只是取消dataTask (即使您取消请求也会调用其完成处理程序)并调用super实现。

  3. 第三种情况是您有一些定期检查isCancelled状态的操作。 在这种情况下,您根本不必实现cancel ,因为默认行为就足够了。 例如:

     class DisplayLinkOperation: AsynchronousOperation { private weak var displayLink: CADisplayLink? private var startTime: CFTimeInterval! private let duration: CFTimeInterval = 2 override func main() { startTime = CACurrentMediaTime() let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:))) displayLink.add(to: .main, forMode: .commonModes) self.displayLink = displayLink } @objc func handleDisplayLink(_ displayLink: CADisplayLink) { let percentComplete = (CACurrentMediaTime() - startTime) / duration if percentComplete >= 1.0 || isCancelled { displayLink.invalidate() finish() } // now do some UI update based upon `elapsed` } } 

    在这种情况下,我在一个操作中包装了一个显示链接,所以我可以管理依赖项和/或将显示链接封装在一个方便的对象中,我根本不需要实现cancel ,因为默认实现将更新isCancelled对我来说,我可以检查一下。

这些是我通常看到的三种基本cancel模式。 已经说过,更新markAsCompleted只触发isFinished如果isExecuting是一个很好的安全检查,以确保你永远不会遇到你描述的问题。


顺便说一句,我用于上述示例的AsynchronousOperation如下所示,改编自试图理解异步操作子类 。 BTW,你所谓的markAsCompleted被称为finish ,听起来你正在通过一种不同的机制触发isFinishedisExecuting KVO,但这个想法基本相同。 在触发isFinished KVO之前,只需检查当前状态:

 open class AsynchronousOperation: Operation { /// State for this operation. @objc private enum OperationState: Int { case ready case executing case finished } /// Concurrent queue for synchronizing access to `state`. private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent) /// Private backing stored property for `state`. private var rawState: OperationState = .ready /// The state of the operation @objc private dynamic var state: OperationState { get { return stateQueue.sync { rawState } } set { stateQueue.sync(flags: .barrier) { rawState = newValue } } } // MARK: - Various `Operation` properties open override var isReady: Bool { return state == .ready && super.isReady } public final override var isExecuting: Bool { return state == .executing } public final override var isFinished: Bool { return state == .finished } public final override var isAsynchronous: Bool { return true } // MARK: - KVN for dependent properties open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set { if ["isReady", "isFinished", "isExecuting"].contains(key) { return [#keyPath(state)] } return super.keyPathsForValuesAffectingValue(forKey: key) } // MARK: - Foundation.Operation public final override func start() { if isCancelled { finish() return } state = .executing main() } /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception. open override func main() { fatalError("Subclasses must implement `main`.") } /// Call this function to finish an operation that is currently executing public final func finish() { if isExecuting { state = .finished } } }