简体   繁体   English

异步操作可以与 OperationQueue 上的“进度”一起使用吗?

[英]Can asynchronous operations be used with `progress` on OperationQueue?

Starting in iOS13, one can monitor the progress of an OperationQueue using the progress property.从 iOS13 开始,可以使用progress属性监控OperationQueue的进度。 The documentation states that only operations that do not override start() count when tracking progress.该文档指出,在跟踪进度时,只有不覆盖start()的操作才会计数。 However, asynchronous operations must override start() and not call super() according to the documentation.但是,根据文档,异步操作必须覆盖start()而不是调用super()

Does this mean asynchronous operations and progress are mutually exclusive (ie only synchronous operations can be used with progress)?这是否意味着asynchronous操作和progress是互斥的(即只有同步操作可以与进度一起使用)? This seems like a massive limitation if this is the case.如果是这样的话,这似乎是一个巨大的限制。

In my own project, I removed my override of start() and everything appears to work okay (eg dependencies are only started when isFinished is set to true on the dependent operation internally in my async operation base class).在我自己的项目中,我删除了对start()的覆盖,并且一切似乎都可以正常工作(例如,仅当我的异步操作基类内部的依赖操作的isFinished设置为true时,才会启动依赖项)。 BUT, this seems risky since Operation explicitly states to override start() .但是,这似乎有风险,因为Operation明确声明要覆盖start()

Thoughts?想法?

Documentaiton references:文献参考:

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

By default, OperationQueue doesn't report progress until totalUnitCount is set.默认情况下,OperationQueue 在设置 totalUnitCount 之前不会报告进度。 When totalUnitCount is set, the queue begins reporting progress.当设置了 totalUnitCount 时,队列开始报告进度。 Each operation in the queue contributes one unit of completion to the overall progress of the queue for operations that are finished by the end of main().对于在 main() 结束时完成的操作,队列中的每个操作都会为队列的整体进度贡献一个完成单位。 Operations that override start() and don't invoke super don't contribute to the queue's progress.覆盖 start() 且不调用 super 的操作不会影响队列的进度。

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

If you are implementing a concurrent operation, you must override this method and use it to initiate your operation.如果您正在实现并发操作,则必须覆盖此方法并使用它来启动您的操作。 Your custom implementation must not call super at any time.您的自定义实现不得在任何时候调用 super。 In addition to configuring the execution environment for your task, your implementation of this method must also track the state of the operation and provide appropriate state transitions.除了为您的任务配置执行环境之外,此方法的实现还必须跟踪操作的 state 并提供适当的 state 转换。

Update: I ended up ditching my AysncOperation for a simple SyncOperation that waits until finish() is called (using a semaphore).更新:我最终放弃了我的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()
    }
}

You are combining two different but related concepts;您正在结合两个不同但相关的概念; asynchronous and concurrency.异步和并发。

An OperationQueue always dispatches Operations onto a separate thread so you do not need to make them explicitly make them asynchronous and there is no need to override start() . OperationQueue总是将Operations分派到单独的线程上,因此您不需要显式地使它们异步,也不需要覆盖start() You should ensure that your main() does not return until the operation is complete.您应该确保您的main()在操作完成之前不会返回。 This means blocking if you perform asynchronous tasks such as network operations.如果您执行异步任务(例如网络操作),这意味着阻塞。

It is possible to execute an Operation directly.可以直接执行Operation In the case where you want concurrent execution of those operations you need to make them asynchronous.如果您希望同时执行这些操作,则需要使它们异步。 It is in this situation that you would override start()在这种情况下,您将覆盖start()

If you want to implement a concurrent operation—that is, one that runs asynchronously with respect to the calling thread—you must write additional code to start the operation asynchronously.如果要实现并发操作(即相对于调用线程异步运行的操作),则必须编写额外的代码来异步启动操作。 For example, you might spawn a separate thread, call an asynchronous system function, or do anything else to ensure that the start method starts the task and returns immediately and, in all likelihood, before the task is finished.例如,您可能会生成一个单独的线程,调用异步系统 function,或执行其他任何操作来确保 start 方法启动任务并立即返回,并且很可能在任务完成之前返回。

Most developers should never need to implement concurrent operation objects.大多数开发人员应该永远不需要实现并发操作对象。 If you always add your operations to an operation queue, you do not need to implement concurrent operations.如果您总是将操作添加到操作队列中,则无需实现并发操作。 When you submit a nonconcurrent operation to an operation queue, the queue itself creates a thread on which to run your operation.当您将非并发操作提交到操作队列时,队列本身会创建一个线程来运行您的操作。 Thus, adding a nonconcurrent operation to an operation queue still results in the asynchronous execution of your operation object code.因此,将非并发操作添加到操作队列仍会导致操作 object 代码的异步执行。 The ability to define concurrent operations is only necessary in cases where you need to execute the operation asynchronously without adding it to an operation queue.只有在需要异步执行操作而不将其添加到操作队列的情况下,才需要定义并发操作的能力。

In summary, make sure your operations are synchronous and do not override start总之,确保您的操作是同步的,并且不要覆盖start

You ask:你问:

Does this mean asynchronous operations and progress are mutually exclusive (ie only synchronous operations can be used with progress)?这是否意味着异步操作和进度是互斥的(即只有同步操作可以与进度一起使用)? This seems like a massive limitation if this is the case.如果是这样的话,这似乎是一个巨大的限制。

Yes, if you implement start , you have to add the operation's child Progress to the queue's parent progress yourself.是的,如果您实现start ,您必须自己将操作的子Progress添加到队列的父progress中。 (It is a little surprising that they did not have the base operation update the progress by observing the isFinished KVO, but it is what it is. Or they could have used the becomeCurrent(withPendingUnitCount:) - resignCurrent pattern, and then this fragile behavior would not exist.) (有点令人惊讶的是,他们没有通过观察isFinished KVO 来更新基本操作的进度,但事实就是如此。或者他们可以使用becomeCurrent(withPendingUnitCount:) - resignCurrent模式,然后使用这种脆弱的行为不会存在。)

But I would not abandon asynchronous operations solely because you want their Progress .但我不会仅仅因为你想要他们的Progress就放弃异步操作。 By making your operation synchronous, you would unnecessarily tie up one of the very limited number of worker threads for the duration of the operation.通过使您的操作同步,您将在操作期间不必要地占用数量非常有限的工作线程之一。 That is the sort of decision that seems very convenient, might not introduce immediate problems, but longer-term might introduce problems that are exceedingly hard to identify when you unexpectedly exhaust your worker thread pool.这是一种看起来非常方便的决定,可能不会立即出现问题,但长期可能会在您意外耗尽工作线程池时引入非常难以识别的问题。

Fortunately, adding our own child Progress is exceedingly simple.幸运的是,添加我们自己的子Progress非常简单。 Consider a custom operation with its own child 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()
        }
    }
}

And then, while adding them to your queue, add the operation's progress as a child of the operation queue's Progress :然后,在将它们添加到您的队列时,将操作的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()
        }
    }
}

It is trivial to add the Progress of your own, custom, asynchronous, Operation subclasses to the operation queue's Progress .将您自己的自定义异步Operation子类的Progress添加到操作队列的Progress是微不足道的。 Or, you might just create your own parent Progress and bypass the progress of the OperationQueue entirely.或者,您可能只是创建自己的父Progress并完全绕过OperationQueueprogress But either way, it is exceedingly simple and there is no point in throwing the baby (asynchronous custom Operation subclass) away with the bathwater.但无论哪种方式,它都非常简单,将婴儿(异步自定义Operation子类)与洗澡水一起扔掉是没有意义的。


If you want, you could simplify the calling point even further, eg, define protocol for operations with Progress :如果您愿意,您可以进一步简化调用点,例如,使用Progress定义操作协议:

protocol Progressive {
    var progress: Progress { get }
}

typealias ProgressOperation = Operation & Progressive

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

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

    override func main() { ... }
}

And then when adding operations:然后在添加操作时:

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

for _ in 0 ..< 10 {
    queue.addOperation(progressOperation: TestOperation())
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM