How to cancel one of many threads in swift - ios

I have a function that starts 3 asynchronous threads. Each thread do something which take some time. When some thread finish first, I need it to stop the other 2, but I don't know how to do it (yet).
My code:
class SomeController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
threads();
}
func threads(){
let operationQueue = NSOperationQueue();
operationQueue.addOperationWithBlock(
{
let thread = NSThread.currentThread();
let threadNumber = thread.valueForKeyPath("private.seqNum").integerValue;
NSThread.sleepForTimeInterval(30);
println("Task #1 completed on thread #\(threadNumber)");
})
operationQueue.addOperationWithBlock(
{
let thread = NSThread.currentThread();
let threadNumber = thread.valueForKeyPath("private.seqNum").integerValue;
NSThread.sleepForTimeInterval(20);
println("Task #2 completed on thread #\(threadNumber)");
})
operationQueue.addOperationWithBlock(
{
let thread = NSThread.currentThread();
let threadNumber = thread.valueForKeyPath("private.seqNum").integerValue;
NSThread.sleepForTimeInterval(5);
println("Task #3 completed on thread #\(threadNumber)");
})
}}

This isn't really a question about canceling an NSThread. Rather, it's a question of how to cancel an NSOperation. In this case, it's relatively easy to cancel all the operations since you're creating a single NSOperationQueue for the sole purpose of executing your three blocks. Just send the operationQueue a cancelAllOperations message:
operationQueue.cancelAllOperations()
The unfortunate part is that operation canceling is really cooperative, so within the operation you have to periodically check isCancelled and terminate as required:
var operation3:NSOperation?
operation3 = operationQueue.addOperationWithBlock {
let thread = NSThread.currentThread()
let threadNumber = thread.valueForKeyPath("private.seqNum").integerValue
var timeout = 5
while timeout-- > 0 && !operation3.isCancelled {
NSThread.sleepForTimeInterval(1)
}
println("Task #3 completed on thread #\(threadNumber)")
operationQueue.cancelAllOperations()
}

If you create an operation with addOperationWithBlock, you can cancel it all you like, it has no effect. If you want to cancel an operation, I recommend not using a block, but subclassing NSOperation. The task that is getting executed must check manually when it's cancelled and finish the task; you can't do that with addOperationWithBlock.

Related

How to get cancellation state for multiple DispatchWorkItems

Background
I'm implementing a search. Each search query results in one DispatchWorkItem which is then queued for execution. As the user can trigger a new search faster than the previous one can be completed, I'd like to cancel the previous one as soon as I receive a new one.
This is my current setup:
var currentSearchJob: DispatchWorkItem?
let searchJobQueue = DispatchQueue(label: QUEUE_KEY)
func updateSearchResults(for searchController: UISearchController) {
let queryString = searchController.searchBar.text?.lowercased() ?? ""
// if there is already an (older) search job running, cancel it
currentSearchJob?.cancel()
// create a new search job
currentSearchJob = DispatchWorkItem() {
self.filter(queryString: queryString)
}
// start the new job
searchJobQueue.async(execute: currentSearchJob!)
}
Problem
I understand that dispatchWorkItem.cancel() doesn't kill the running task immediately. Instead, I need to check for dispatchWorkItem.isCancelled manually. But how do I get the right dispatchWorkItemobject in this case?
If I were setting currentSearchJob only once, I could simply access that attribute like done in this case. However, this isn't applicable here, because the attribute will be overriden before the filter() method will be finished. How do I know which instance is actually running the code in which I want to check for dispatchWorkItem.isCancelled?
Ideally, I'd like to provide the newly-created DispatchWorkItem as an additional parameter to the filter() method. But that's not possible, because I'll get a Variable used within its own initial value error.
I'm new to Swift, so I hope I'm just missing something. Any help is appreciated very much!
The trick is how to have a dispatched task check if it has been canceled. I'd actually suggest consider OperationQueue approach, rather than using dispatch queues directly.
There are at least two approaches:
Most elegant, IMHO, is to just subclass Operation, passing whatever you want to it in the init method, and performing the work in the main method:
class SearchOperation: Operation {
private var queryString: String
init(queryString: String) {
self.queryString = queryString
super.init()
}
override func main() {
// do something synchronous, periodically checking `isCancelled`
// e.g., for illustrative purposes
print("starting \(queryString)")
for i in 0 ... 10 {
if isCancelled { print("canceled \(queryString)"); return }
print(" \(queryString): \(i)")
heavyWork()
}
print("finished \(queryString)")
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
}
Because that's in an Operation subclass, isCancelled is implicitly referencing itself rather than some ivar, avoiding any confusion about what it's checking. And your "start a new query" code can just say "cancel anything currently on the the relevant operation queue and add a new operation onto that queue":
private var searchQueue: OperationQueue = {
let queue = OperationQueue()
// queue.maxConcurrentOperationCount = 1 // make it serial if you want
queue.name = Bundle.main.bundleIdentifier! + ".backgroundQueue"
return queue
}()
func performSearch(for queryString: String) {
searchQueue.cancelAllOperations()
let operation = SearchOperation(queryString: queryString)
searchQueue.addOperation(operation)
}
I recommend this approach as you end up with a small cohesive object, the operation, that nicely encapsulates a block of work that you want to do, in the spirit of the Single Responsibility Principle.
While the following is less elegant, technically you can also use BlockOperation, which is block-based, but for which which you can decouple the creation of the operation, and the adding of the closure to the operation. Using this technique, you can actually pass a reference to the operation to its own closure:
private weak var lastOperation: Operation?
func performSearch(for queryString: String) {
lastOperation?.cancel()
let operation = BlockOperation()
operation.addExecutionBlock { [weak operation, weak self] in
print("starting \(identifier)")
for i in 0 ... 10 {
if operation?.isCancelled ?? true { print("canceled \(identifier)"); return }
print(" \(identifier): \(i)")
self?.heavyWork()
}
print("finished \(identifier)")
}
searchQueue.addOperation(operation)
lastOperation = operation
}
func heavyWork() {
Thread.sleep(forTimeInterval: 0.5)
}
I only mention this for the sake of completeness. I think the Operation subclass approach is frequently a better design. I'll use BlockOperation for one-off sort of stuff, but as soon as I want more sophisticated cancelation logic, I think the Operation subclass approach is better.
I should also mention that, in addition to more elegant cancelation capabilities, Operation objects offer all sorts of other sophisticated capabilities (e.g. asynchronously manage queue of tasks that are, themselves, asynchronous; constrain degree of concurrency; etc.). This is all beyond the scope of this question.
you wrote
Ideally, I'd like to provide the newly-created DispatchWorkItem as an
additional parameter
you are wrong, to be able to cancel running task, you need a reference to it, not to the next which is ready to dispatch.
cancel() doesn't cancel running task, it only set internal "isCancel" flag by the thread-safe way, or remove the task from the queue before execution. Once executed, checking isCancel give you a chance to finish the job (early return).
import PlaygroundSupport
import Foundation
PlaygroundPage.current.needsIndefiniteExecution = true
let queue = DispatchQueue.global(qos: .background)
let prq = DispatchQueue(label: "print.queue")
var task: DispatchWorkItem?
func work(task: DispatchWorkItem?) {
sleep(1)
var d = Date()
if task?.isCancelled ?? true {
prq.async {
print("cancelled", d)
}
return
}
sleep(3)
d = Date()
prq.async {
print("finished", d)
}
}
for _ in 0..<3 {
task?.cancel()
let item = DispatchWorkItem {
work(task: task)
}
item.notify(queue: prq) {
print("done")
}
queue.asyncAfter(deadline: .now() + 0.5, execute: item)
task = item
sleep(1) // comment this line
}
in this example, only the very last job is really fully executed
cancelled 2018-12-17 23:49:13 +0000
done
cancelled 2018-12-17 23:49:14 +0000
done
finished 2018-12-17 23:49:18 +0000
done
try to comment the last line and it prints
done
done
finished 2018-12-18 00:07:28 +0000
done
the difference is, that first two execution never happened. (were removed from the dispatch queue before execution)

What's the difference between operation and block in NSOperation?

When I use following codes:
let queue = OperationQueue()
let operation = BlockOperation()
for i in 0..<10 {
operation.addExecutionBlock({
print("===\(Thread.current)===\(i)"
})
}
queue.addOperation(operation)
I create a asynchronous queue to execute these operations.
And if I use codes like following:
let queue = OperationQueue()
for i in 0..<10 {
queue.addOperation(
print("===\(Thread.current)===\(i)"
)
}
When I make the queue concurrent,they produce the same result.
But when I set
queue.maxConcurrentOperationCount = 1
to make the queue serial, they are different!
The first one still print the unordered result like the concurrent queue. But the second one can print the ordered result.
So what's the difference between them? When I want to use NSOperation, which one should I use? Any help much appreciated!
The docs on OperationQueue tell you about concurrency and order of execution of the blocks you submit. You should read the Xcode article on OperationQueue. Here is a relevant bit:
An operation queue executes its queued operation objects based on
their priority and readiness. If all of the queued operation objects
have the same priority and are ready to execute when they are put in
the queue—that is, their isReady method returns true—they are executed
in the order in which they were submitted to the queue. However, you
should never rely on queue semantics to ensure a specific execution
order of operation objects. Changes in the readiness of an operation
can change the resulting execution order. If you need operations to
execute in a specific order, use operation-level dependencies as
defined by the Operation class.
Check please the official documentation regarded addExecutionBlock: function. It just adds the specified block to the receiver's list of blocks to perform in context of executing operation.
If you would like to do it synchronously, here is a code sample:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
for i in 0..<10 {
let operation = BlockOperation {
print("===\(Thread.current)===\(i)")
}
queue.addOperation(operation)
}
When I want to use NSOperation, which one should I use?
Use the second one.
Just a guess.
In this case:
let queue = OperationQueue()
let operation = BlockOperation()
for i in 0..<10 {
operation.addExecutionBlock({
print("===\(Thread.current)===\(i)"
})
}
queue.addOperation(operation)
Inside the BlockOperation, blocks are asynchronous while the BlockOperation itself
is synchronous. So it actually is a synchronous queue.
So the use of queue.addOperation(operation) is nonsense. Instead of it,
I should use operation.start() because this is a synchronous queue.
The function addExecutionBlock() should be used when you need a synchronous queue.
The function addOperation() should be used when you need a asynchronous queue.
Difference -> BlockOperation has a addDependency whereas OperationQueue() needs to addOperations. Following code with console output will elaborate:
let opQueue = OperationQueue()
opQueue.addOperation {
print("operation 1")
}
let operation2 = BlockOperation {
print("operation 2")
}
let operation3 = BlockOperation {
print("operation 3")
}
operation2.addDependency(operation3)
opQueue.addOperation(operation2)
opQueue.addOperation(operation3)
Console output:
operation 1
operation 3
operation 2

How to wait until all NSOperations is finished?

I have the following code:
func testFunc(completion: (Bool) -> Void) {
let queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
for i in 1...3 {
queue.addOperationWithBlock{
Alamofire.request(.GET, "https://httpbin.org/get").responseJSON { response in
switch (response.result){
case .Failure:
print("error")
break;
case .Success:
print("i = \(i)")
}
}
}
//queue.addOperationAfterLast(operation)
}
queue.waitUntilAllOperationsAreFinished()
print("finished")
}
and output is:
finished
i = 3
i = 1
i = 2
but I expect the following:
i = 3
i = 1
i = 2
finished
So, why queue.waitUntilAllOperationsAreFinished() don't wait?
Each operation you've added into queue is immediately executed because Alamofire.request simply returns without waiting for the response data.
Furthermore, there is a possibility of deadlock there. Since responseJSON block is executed within the main queue by default, blocking the main thread by calling waitUntilAllOperationsAreFinished will prevent it from executing the completion block at all.
First, in order to fix the deadlock issue, you can tell Alamofire to execute the completion block in a different queue, second, you can use dispatch_group_t to group the number of asynchronous HTTP requests and keep the main thread waiting till all those requests in the group finish executing:
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let group = dispatch_group_create()
for i in 1...3 {
dispatch_group_enter(group)
Alamofire.request(.GET, "https://httpbin.org/get").responseJSON(queue: queue, options: .AllowFragments) { response in
print(i)
dispatch_async(dispatch_get_main_queue()) {
// Main thread is still blocked. You can update the UI here but it will take effect after all HTTP requests are finished.
}
dispatch_group_leave(group)
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
print("finished")
I would suggest you to use KVO and observe when the queue has finish all the task instead of blocking the current thread until all the operations finished. Or you can use dependencies. Take a look at this SO question
To check whether all operations finished - We could use KVO to observe number of operations in the Queue. Unfortunately both operations and operationCount are currently deprecated..!
So it's safe to use following option using dependency.
To check few operations are finished - Use Dependencies :
Create a final operation called "finishOperation" then add dependencies to all other required operation. This way, "finishOperation" will be executed only when depended operations are finished. Check this answer for code sample.

Alamofire multitask Execution over after one func

use Alamofire multitask Execution over after one func.
my use gcd,NSOperationQueue all failure.
Please help me to solve the master.
The following pseudo code:
let imgDatas1 = UIImageJPEGRepresentation(UIImage(named: "aar")!, 0.1)
let strUrl1 = "http://www.baidu.com"
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//let queue = dispatch_get_main_queue()
dispatch_group_async(group, queue) {
print("threed 1.1")
Alamofire.upload(.POST, strUrl1, data: imgDatas1!).responseString(completionHandler: { (dd) in
NSThread.sleepForTimeInterval(3.0)
print("threed1.3")
})
print("threed1.2")
}
dispatch_group_async(group, queue) {
print("threed2.1")
Alamofire.upload(.POST, strUrl1, data: imgDatas1!).responseString(completionHandler: { (dd) in
NSThread.sleepForTimeInterval(2.0)
print("threed2.3")
})
print("threed2.2")
}
dispatch_group_notify(group, queue) {
print("voer")
}
let operationQueue = NSOperationQueue()
let operation1 = NSBlockOperation {
Alamofire.upload(.POST, strUrl1, data: imgDatas1!).responseString(completionHandler: { (dd) in
NSThread.sleepForTimeInterval(2.0)
print("xian 1.2")
})
print("xian 1.1")
}
let operation2 = NSBlockOperation {
Alamofire.upload(.POST, strUrl1, data: imgDatas1!).responseString(completionHandler: { (dd) in
NSThread.sleepForTimeInterval(3.0)
print("xian 2.2")
})
print("xian 2.1")
}
let operation3 = NSBlockOperation {
print("xian 3")
}
operation2.addDependency(operation1)
operation3.addDependency(operation2)
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)
operationQueue.addOperation(operation3)
The problem with both of these approaches is that you're synchronizing the issuing of the requests, but not the actual response of the request. In your GCD example, you're exiting your dispatch_group_async as soon as the request is issued, although the response has not yet been received. Likewise in your operation queue example, you're completing your block operation as soon as the request is issued, but these operations aren't waiting for the requests to finish.
The simple, if inelegant approach is to just call one in the completion handler of the prior one. If you put them in separate functions, it avoids the unseemly nesting of completion handlers.
If you're looking for a more elegant solution, you solve this with operation queues by wrapping this in an asynchronous NSOperation/Operation subclass, and only trigger the isFinished KVO when the request is done. See Asynchronous Versus Synchronous Operations section of the Operation Reference, or the slightly more detailed (though dated) discussion in Operation Queues: Concurrent Versus Non-concurrent Operations in the Concurrency Programming Guide.
Another elegant approach is to use promises, something like PromiseKit. It strikes me as a fairly dramatic solution (introduce an entirely new asynchronous pattern), but it does solve this sort of issue well.

Why is my NSOperationQueue running on main thread?

I have set up an operation queue:
func initialiseOperationQueue(){
self.operationQueue = NSOperationQueue()
self.operationQueue.name = "General queue"
self.operationQueue.maxConcurrentOperationCount = 2
}
Then I added an operation to my queue
let op = HPSyncDataOperation(type: HPSyncDataOperationType.OnlineRecord, delegate: self, date: self.latestLastUpdateAt)
self.operationQueue.addOperation(op)
It is basically using Parse framework to asynchronously download some record data online. Its implementation looks like the following:
PFCloud.callFunctionInBackground("recordPosts", withParameters: param, block: { (objects:AnyObject!, error:NSError!) -> Void in
if error == nil {
let dataObjects = objects as [PFObject]
//TROUBLE HERE:
for object in dataObjects {
object.pinWithName("Received Posts")
}
//abcdefg
}
})
But in execution, when object.pinWithName("Received Posts") is run, it invokes
Warning: A long-running operation is being executed on the main thread.
Should an operation be run on a separate thread? So pinWithName, regardless of its sync or async, should be run on a separate thread as well?
Please help! Why is this?
Your operation will be run on a background thread, but all it's doing is starting another asynchronous process (PFCloud.callFunctionInBackground) which will start another thread. When that other process is complete it calls the completion block on the main thread.
So, in this case your operation and queue are doing basically nothing, and really you should be taking the result of the call to PFCloud.callFunctionInBackground (i.e. objects) and processing that on a background thread if it's likely to be time consuming.

Resources