【问题标题】:NSOperation Queue waitUntilAllOperationsAreFinished causes NSOperation instance deinit lateNSOperation Queue waitUntilAllOperationsAreFinished 导致 NSOperation 实例 deinit 延迟
【发布时间】:2021-09-15 03:55:29
【问题描述】:

我们的应用目前使用NSOperation(Swift 中的Operation)来管理一系列网络请求和数据解析。 需要在队列中的所有5个操作都完成后执行一些代码,通常使用GCD组实现。

DispatchQueue.global().async {
    (0...5).forEach(){
        self.queue.addOperation(CustomOperation(value: $0))
    }
    self.queue.waitUntilAllOperationsAreFinished()
    print("All Tasks Done")
}

问题是 NSOperation 实例而不是 deinit 直到所有 5 个操作完成,这导致内存释放比预期的要晚。 如果queue.waitUntilAllOperationsAreFinished 被删除,实例将立即变为deinit

我们添加了自动释放池来避免它。但是在使用waitUntilAllOperationsAreFinished 时是否可以立即将NSOperation 实例化为deinit

waitUntilAllOperationsAreFinished 打印

Begin Task 5
Begin Task 4
Begin Task 3
Begin Task 2
Begin Task 1
Begin Task 0
Finish Task 0
Finish Task 1
Finish Task 2
Finish Task 3
Finish Task 4
Finish Task 5
deinit 0
deinit 1
deinit 2
deinit 3
deinit 4
deinit 5
All Tasks Done

打印不带waitUntilAllOperationsAreFinished

All Tasks Done
Begin Task 0
Begin Task 1
Begin Task 4
Begin Task 3
Begin Task 5
Finish Task 0
Begin Task 2
deinit 0
Finish Task 1
deinit 1
Finish Task 2
deinit 2
Finish Task 3
deinit 3
Finish Task 4
deinit 4
Finish Task 5
deinit 5

自定义操作。

class CustomOperation: Operation {
    
    public enum State {
        case ready
        case running
        case finished
    }

    private var state: State = .ready
    
    override var isAsynchronous: Bool { return true }
    
    override open var isExecuting: Bool { state == .running }

    override open var isFinished: Bool { state == .finished }

    var value: Int = 0
    
    init(value: Int) {
        super.init()
        
        self.value = value
    }
    
    override func main() {
        print("Begin Task \(value)")
        DispatchQueue.global().asyncAfter(deadline: .now()+DispatchTimeInterval.seconds(value)) {
            print("Finish Task \(self.value)")
            self.finish()
        }
    }
    
    func finish() {
        willChangeValue(forKey: "isExecuting")
        willChangeValue(forKey: "isFinished")
        state = .finished
        didChangeValue(forKey: "isFinished")
        didChangeValue(forKey: "isExecuting")
    }
    
    deinit {
        print("deinit")
    }
}

【问题讨论】:

  • 与手头的问题无关,但这不是线程安全的。正如the docs 所说,“如果您在子类中实现自定义方法,例如自定义数据访问器,您还必须确保这些方法是线程安全的。”另外,请注意,您永远不会将state 设置为.running(我通常在start 中设置,但您也可以在main 中设置)。此外,我们通常也有cancelled 的状态。但这些都与手头的问题无关,只是一个友好的观察。
  • FWIW,而不是 waitUntilAllOperationsAreFinished(阻塞有限数量的工作线程之一),我们将使用完成操作(w 依赖项)或屏障操作。它们都遇到了与这里相同的问题,但它们比waitUntilAllOperationsAreFinished 更好(没有阻塞的线程,不需要将其分派到全局队列等)。在回答您的问题时,我们只需让 finish 函数从内存中释放任何对象。该操作本身占用的内存非常少,因此将您的内存消耗与释放操作时分离。

标签: swift nsoperationqueue nsoperation


【解决方案1】:

不知道这种行为,但我认为你不能在这里做点什么。
如果您关心操作顺序,您可以将队列设置为将maxConcurrentOperationCount 设置为 1,以便您可以保持顺序。
如果您担心内存并且您有一些庞大的数据,您可以在 finish() 方法中删除它或使用 completionBlock 传递它。
OperationQueue 属性上还可以选择使用 KVO,它的大部分属性都符合 KVO 和 KVC,您可以对其中一些属性设置观察以触发回调。
如果您正在部署 target >=13,您可以使用 vadian 已经编写的 Combine。

【讨论】:

  • 关于简单地清理finish 中的数据的好答案。这是正确的答案。如果您关心顺序,那么关于牺牲并发性的题外话是不正确的:整理响应以使它们以正确的顺序排列,但不要像那样不必要地牺牲性能。
【解决方案2】:

等待更好的方法是例如观察操作计数

import Combine

DispatchQueue.global().async {
     (0...5).forEach {
         queue.addOperation(CustomOperation(value: $0))
     }
 }

var store : AnyCancellable?

store = queue.publisher(for: \.operationCount)
    .sink { value in
        if value == 0 { print("All Tasks Done")}
    }

我的建议是使用Combine,传统的KVO也可以

【讨论】:

  • 我建议如果你要使用Combine,那么根本不需要这些操作。它有自己的异步方法模式,控制并发程度。如果你跳到合并,我建议完全取消操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多