Reputation: 5781
I have this code to add a NSOperation
instance to a queue
let operation = NSBlockOperation()
operation.addExecutionBlock({
self.asyncMethod() { (result, error) in
if operation.cancelled {
return
}
// etc
}
})
operationQueue.addOperation(operation)
When user leaves the view that triggered this above code I cancel operation doing
operationQueue.cancelAllOperations()
When testing cancelation, I'm 100% sure cancel is executing before async method returns so I expect operation.cancelled
to be true. Unfortunately this is not happening and I'm not able to realize why
I'm executing cancellation on viewWillDisappear
EDIT
asyncMethod
contains a network operation that runs in a different thread. That's why the callback is there: to handle network operation returns. The network operation is performed deep into the class hierarchy but I want to handle NSOperations at root level.
Upvotes: 6
Views: 5666
Reputation: 2173
I have the same problem.
I'm sure the start()
was called after calling cancelAllOperations()
.
But the isCancelled
was always false
, and the cancel()
was never called.
So I decided to use an array to store all the operations to call cancel()
one by one.
func cancel() {
for operation in operations {
operation.cancel()
}
operations.removeAll()
}
class UploadOperation: Operation {
var uploadProcessing = false;
var uploadFinished = false;
var uploadCanceled = false;
override var isAsynchronous: Bool {
return true
}
override var isExecuting: Bool {
return uploadProcessing
}
override var isFinished: Bool {
return uploadFinished
}
override var isCancelled: Bool {
return uploadCanceled
}
override func cancel() {
super.cancel()
willChangeValue(forKey: #keyPath(isCancelled))
uploadCanceled = true;
didChangeValue(forKey: #keyPath(isCancelled))
}
override func start() {
willChangeValue(forKey: #keyPath(isExecuting))
uploadProcessing = true
didChangeValue(forKey: #keyPath(isExecuting))
guard !uploadCanceled else {
return
}
}
}
Just a workaround, it looks not pretty but works.
Upvotes: 0
Reputation: 12687
it's because you doing work wrong. You cancel operation after it executed. Check this code, block executed in one background thread. Before execution start – operation cancel, remove first block from queue.
let operationQueue = OperationQueue()
operationQueue.qualityOfService = .background
let ob1 = BlockOperation {
print("ExecutionBlock 1. Executed!")
}
let ob2 = BlockOperation {
print("ExecutionBlock 2. Executed!")
}
operationQueue.addOperation(ob1)
operationQueue.addOperation(ob2)
ob1.cancel()
// ExecutionBlock 2. Executed!
let operationQueue = NSOperationQueue()
operationQueue.qualityOfService = .Background
let ob1 = NSBlockOperation()
ob1.addExecutionBlock {
print("ExecutionBlock 1. Executed!")
}
let ob2 = NSBlockOperation()
ob2.addExecutionBlock {
print("ExecutionBlock 2. Executed!")
}
operationQueue.addOperation(ob1)
operationQueue.addOperation(ob2)
ob1.cancel()
// ExecutionBlock 2. Executed!
Upvotes: 2
Reputation: 19124
It does not make sense to put an asynchronous function into a block with NSBlockOperation
. What you probably want is a proper subclass of NSOperation
as a concurrent operation which executes an asynchronous work load. Subclassing an NSOperation
correctly is however not that easy as it should.
You may take a look here reusable subclass for NSOperation for an example implementation.
Upvotes: 1
Reputation: 1197
The Operation does not wait for your asyncMethod
to be finished. Therefore, it immediately returns if you add it to the Queue. And this is because you wrap your async network operation in an async NSOperation
.
NSOperation is designed to give a more advanced async handling instead for just calling performSelectorInBackground
. This means that NSOperation
is used to bring complex and long running operations in background and not block the main thread. A good article of a typically used NSOperation
can be found here:
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
For your particular use case, it does not make sense to use an NSOperation here, instead you should just cancel your running network request.
Upvotes: 1
Reputation: 2724
I am not 100% sure what you are looking for, but maybe what you need is to pass the operation, as parameter, into the asyncMethod()
and test for cancelled state in there?
operation.addExecutionBlock({
asyncMethod(operation) { (result, error) in
// Result code
}
})
operationQueue.addOperation(operation)
func asyncMethod(operation: NSBlockOperation, fun: ((Any, Any)->Void)) {
// Do stuff...
if operation.cancelled {
// Do something...
return // <- Or whatever makes senes
}
}
Upvotes: 0
Reputation: 17572
Calling the cancel method of this object sets the value of this property to YES. Once canceled, an operation must move to the finished state.
Canceling an operation does not actively stop the receiver’s code from executing. An operation object is responsible for calling this method periodically and stopping itself if the method returns YES.
You should always check the value of this property before doing any work towards accomplishing the operation’s task, which typically means checking it at the beginning of your custom main method. It is possible for an operation to be cancelled before it begins executing or at any time while it is executing. Therefore, checking the value at the beginning of your main method (and periodically throughout that method) lets you exit as quickly as possible when an operation is cancelled.
import Foundation
let operation1 = NSBlockOperation()
let operation2 = NSBlockOperation()
let queue = NSOperationQueue()
operation1.addExecutionBlock { () -> Void in
repeat {
usleep(10000)
print(".", terminator: "")
} while !operation1.cancelled
}
operation2.addExecutionBlock { () -> Void in
repeat {
usleep(15000)
print("-", terminator: "")
} while !operation2.cancelled
}
queue.addOperation(operation1)
queue.addOperation(operation2)
sleep(1)
queue.cancelAllOperations()
try this simple example in playground.
if it is really important to run another asynchronous code, try this
operation.addExecutionBlock({
if operation.cancelled {
return
}
self.asyncMethod() { (result, error) in
// etc
}
})
Upvotes: 6