2

从 iOS13 开始,可以监控OperationQueue使用progress属性的进度。该文档指出,在跟踪进度时,只有不覆盖start()计数的操作。但是,异步操作必须根据文档覆盖start()而不是调用。super()

这是否意味着asynchronous操作和progress互斥(即只有同步操作可以与进度一起使用)?如果是这样的话,这似乎是一个巨大的限制。

在我自己的项目中,我删除了我的覆盖,start()并且一切似乎isFinished都可以正常工作(例如,依赖关系仅在我的异步操作基类内部设置为true依赖操作时才启动)。但是,这似乎有风险,因为Operation明确声明要覆盖start().

想法?

文献参考:

https://developer.apple.com/documentation/foundation/operationqueue/3172535-progress

默认情况下,OperationQueue 在设置 totalUnitCount 之前不会报告进度。当设置了 totalUnitCount 时,队列开始报告进度。对于在 main() 结束时完成的操作,队列中的每个操作都会为队列的整体进度贡献一个完成单位。覆盖 start() 且不调用 super 的操作不会影响队列的进度。

https://developer.apple.com/documentation/foundation/operation/1416837-start

如果您正在实现并发操作,则必须覆盖此方法并使用它来启动您的操作。您的自定义实现不得在任何时候调用 super。除了为您的任务配置执行环境之外,您对该方法的实现还必须跟踪操作的状态并提供适当的状态转换。

更新:我最终放弃了我AysncOperation的一个简单SyncOperation的等待直到finish()被调用(使用信号量)。

/// A synchronous operation that automatically waits until `finish()` is called.
open class SyncOperation: Operation {

    private let waiter = DispatchSemaphore(value: 0)

    /// Calls `work()` and waits until `finish()` is called.
    public final override func main() {
        work()
        waiter.wait()
    }

    /// The work of the operation. Subclasses must override this function and call `finish()` when their work is done.
    open func work() {
        preconditionFailure("Subclasses must override `work()` and call `finish()`")
    }

    /// Finishes the operation.
    ///
    /// The work of the operation must be completed when called. Failing to call `finish()` is a programmer error.
    final public func finish() {
        waiter.signal()
    }
}
4

2 回答 2

2

您正在结合两个不同但相关的概念;异步和并发。

OperationQueue总是分派到单独的Operations线程上,因此您不需要显式地使它们异步,也不需要覆盖start(). 您应该确保main()在操作完成之前不会返回。如果您执行异步任务(例如网络操作),这意味着阻塞。

可以Operation直接执行。如果您希望同时执行这些操作,则需要使它们异步。在这种情况下,您将覆盖start()

如果要实现并发操作(即相对于调用线程异步运行的操作),则必须编写额外的代码来异步启动操作。例如,您可能会生成一个单独的线程、调用异步系统函数或执行其他任何操作来确保 start 方法启动任务并立即返回,并且很可能在任务完成之前返回。

大多数开发人员应该永远不需要实现并发操作对象。如果您总是将操作添加到操作队列中,则无需实现并发操作。当您将非并发操作提交到操作队列时,队列本身会创建一个线程来运行您的操作。因此,将非并发操作添加到操作队列仍然会导致操作对象代码的异步执行。只有在需要异步执行操作而不将其添加到操作队列的情况下,才需要定义并发操作的能力。

总之,确保您的操作是同步的,start如果您想利用progress

更新

虽然通常的建议是不要尝试使异步任务同步,但在这种情况下,如果您想利用progress. 问题是如果你有一个异步操作,队列不能告诉它什么时候真正完成。如果队列无法判断操作何时完成,那么它就无法progress为该操作准确更新。

您确实需要考虑这样做对线程池的影响。

另一种方法是不使用内置progress功能并创建您自己的属性,您可以从您的任务中更新。

于 2022-01-31T05:45:22.337 回答
2

你问:

这是否意味着异步操作和进度是互斥的(即只有同步操作可以与进度一起使用)?如果是这样的话,这似乎是一个巨大的限制。

是的,如果您实施start,您必须自己将操作的子代添加Progress到队列的父代progress中。(有点奇怪,他们没有通过观察isFinishedKVO 来更新基础操作的进度,但事实就是如此。或者他们可以使用becomeCurrent(withPendingUnitCount:)-resignCurrent模式,然后这种脆弱的行为就不存在了。)

但我不会仅仅因为你想要他们的Progress. 通过使您的操作同步,您将在操作期间不必要地占用数量非常有限的工作线程之一。这是一种看起来非常方便的决定,可能不会立即出现问题,但长期可能会在您意外耗尽工作线程池时引入非常难以识别的问题。

幸运的是,添加我们自己的孩子Progress非常简单。考虑一个带有自己子项的自定义操作Progress

class TestOperation: AsynchronousOperation {
    let progress = Progress(totalUnitCount: 1)

    override func main() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
            progress.completedUnitCount = 1
            finish()
        }
    }
}

然后,在将它们添加到队列时,将操作添加为progress操作队列的子项Progress

class ViewController: UIViewController {
    @IBOutlet weak var progressView: UIProgressView!

    let queue: OperationQueue = ...

    override func viewDidLoad() {
        super.viewDidLoad()

        queue.progress.totalUnitCount = 10
        progressView.observedProgress = queue.progress

        for _ in 0 ..< 10 {
            queue.progress.becomeCurrent(withPendingUnitCount: 1)
            queue.addOperation(TestOperation())
            queue.progress.resignCurrent()
        }
    }
}

Progress您自己的自定义异步Operation子类添加到操作队列的Progress. 或者,您可能只是创建自己的父级Progress并完全绕过。但是无论哪种方式,它都非常简单,并且将婴儿(异步自定义子类)与洗澡水一起扔掉是没有意义的。progressOperationQueueOperation


如果您愿意,您可以进一步简化调用点,例如,使用 定义typealias操作Progress

typealias ProgressOperation = Operation & ProgressReporting

extension OperationQueue {
    func addOperation(progressOperation: ProgressOperation, pendingUnitCount: Int64 = 1) {
        progress.addChild(progressOperation.progress, withPendingUnitCount: pendingUnitCount)
        addOperation(progressOperation)
    }
}

class TestOperation: AsynchronousOperation, ProgressReporting {
    let progress = Progress(totalUnitCount: 1)

    override func main() { ... }
}

然后在添加操作时:

queue.progress.totalUnitCount = 10
progressView.observedProgress = queue.progress

for _ in 0 ..< 10 {
    queue.addOperation(progressOperation: TestOperation())
}
于 2022-02-02T14:54:34.613 回答