操作进行了isFinished = YES而不被它所在的队列启动 [英] Operation went isFinished=YES without being started by the queue it is in
问题描述
- 有一个异步操作子类
- 将此操作添加到队列中.
- 我在开始之前取消了该操作.
SomeOperation went isFinished=YES without being started by the queue it is in
- 这是可以忽略的事情还是严重的事情?
- 如何解决这个问题?
- 最后提供的解决方法/解决方案有效吗?
代码:
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
设置isExecuting = false
和isFinished = true
-
isExecuting
,isFinished
是已同步KVO
的属性
markAsCompleted
setsisExecuting = false
andisFinished = true
isExecuting
,isFinished
are properties which are synchronisedKVO
-
如果我正在处理某种模式,其中任务的取消将阻止其将执行操作转换为
isFinished
状态.
Note:
推荐答案
关键问题在于,当操作不是isExecuting
时,markAsCompleted
会触发isFinished
.我建议您仅将markAsCompleted
修复为仅在isExecuting
为true时执行此操作.这样可以减轻子类进行任何复杂的状态测试以确定是否需要转换为isFinished
的负担.
The key problem is that your markAsCompleted
is triggering isFinished
when the operation is not isExecuting
. I'd suggest you just fix that markAsCompleted
to only do this if isExecuting
is true. This reduces the burden on subclasses doing any complicated state tests to figure out whether they need to transition to isFinished
or not.
话虽如此,在编写可取消的异步操作时,我看到了三种基本模式:
That having been said, I see three basic patterns when writing cancelable asynchronous operations:
在那种情况下,我必须手动执行cancel
实现以完成执行操作.例如:
In that case, I must have cancel
implementation manually finish the executing operations. For example:
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
将明确完成操作本身.
Focusing on the cancel
implementation, because if I cancel the DispatchWorkItem
it won't finish the operation, I therefore need to make sure that cancel
will explicitly finish the operation itself.
有时,当您取消某些异步任务时,它将自动为您调用其完成处理程序,在这种情况下,cancel
除了取消该任务并调用super以外,不需要执行任何其他操作.例如:
Sometimes, when you cancel some asynchronous task, it will call its completion handler automatically for you, in which case cancel
doesn't need to do anything other than cancel the that task and call super. For example:
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
(即使您取消了请求,它也会调用其完成处理程序)并调用
Again, focusing on cancel
, in this case we don't touch the "finished" state, but just cancel dataTask
(which will call its completion handler even if you cancel the request) and call the super
implementation.
第三种情况是您有一些操作正在定期检查isCancelled
状态.在那种情况下,您根本不必实现cancel
,因为默认行为就足够了.例如:
The third scenario is where you have some operation that is periodically checking isCancelled
state. In that case, you don't have to implement cancel
at all, as the default behavior is sufficient. For example:
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
,我可以检查一下.
In this case, where I've wrapped a display link in an operation so I can manage dependencies and/or encapsulate the display link in a convenient object, I don't have to implement cancel
at all, because the default implementation will update isCancelled
for me, and I can just check for that.
我通常会看到三种基本的cancel
模式.话虽如此,将markAsCompleted
更新为仅在isExecuting
时触发isFinished
是很好的安全检查,以确保您永远不会遇到所描述的问题.
Those are three basic cancel
patterns I generally see. That having been said, updating markAsCompleted
to only trigger isFinished
if isExecuting
is a good safety check to make sure you can never get the problem you described.
顺便说一句,我在上面的示例中使用的AsynchronousOperation
如下,改编自试图理解异步操作子类一个>.顺便说一句,您称为markAsCompleted
的东西称为finish
,听起来您是通过不同的机制触发isFinished
和isExecuting
KVO的,但想法基本相同.只需在触发isFinished
KVO之前检查当前状态:
By the way, the AsynchronousOperation
that I used for the above examples is as follows, adapted from Trying to Understand Asynchronous Operation Subclass. BTW, what you called markAsCompleted
is called finish
, and it sounds like you're triggering the isFinished
and isExecuting
KVO via a different mechanism, but the idea is basically the same. Just check the current state before you trigger 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<String> {
if ["isReady", "isFinished", "isExecuting"].contains(key) {
return [#keyPath(state)]
}
return super.keyPathsForValuesAffectingValue(forKey: key)
}
// MARK: - Foundation.Operation
public final override func start() {
if isCancelled {
state = .finished
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 }
}
}
这篇关于操作进行了isFinished = YES而不被它所在的队列启动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!