[英]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
并完全绕过OperationQueue
的progress
。 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.