I realize that this is a problem others have had before me but I seem unable to find a good solution. My problem is that I want to loop through a number of operations stored in a NSOperationQueue, and when all operations have finished I would like to post a notification. The notification starts a final process that relies on all of the operations to have finished. However, my notification is sent several times prior to the operations have finished. This is my approach:
self.queueFinished=FALSE;
[self createFileobjectsForDirectories:self.mymode];
This results in an array of operations (self.arrayOfOperations) which is further used:
[self.myFileobjectsQueue addOperations:self.arrayOfOperations waitUntilFinished:NO];
NSInvocationOperation *completionOperation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(runLastOperation:)
object:start];
for (NSOperation *myOp in self.myFileobjectsQueue.operations){
[completionOperation addDependency:myOp];
}
[completionOperation setQueuePriority:NSOperationQueuePriorityVeryLow];
[self.myFileobjectsQueue addOperation:completionOperation];
-(void) runLastOperation:(NSDate*) start {
[[NSNotificationCenter defaultCenter] postNotificationName:kCompareFinished object:nil userInfo:nil];
self.queueFinished=TRUE;
}
I have tried several approaches to observing when my queue has finished as suggested elsewhere, without success. It is almost like the notifications are called when the queue is empty, but prior to operations have finished their tasks. But according to Apple documentation the operation will not be removed from the queue until the isFinished flag is TRUE. I am running concurrent operations using the regular main function in a subclass of NSOperation. Perhaps I am misunderstanding something here, but suggestions for how to make sure that not only the queue is empty, but to also make sure that the operations have finished would be greatly appreciated.
Cheers, Trond
It doesn't seem like you need a notification (at least not to trigger the final processing). Instead, create a new operation, make it dependent upon all of the other operations that you add for your processing and then add it to the queue. Then it will run only once all of the other operations are complete.
You're correct about isFinished determining when the operation is finished. One thing to consider is that any completionBlock added to an operation is run after the operation has completed (not just before it completes).
Related
I am just wondering at what exact moment a completionBlock is executed on a NSOperation owned by a NSOperationQueue.
On my newest project, a client for Amazon S3 (https://github.com/StudioIstanbul/SIAFAWSClient), I use a NSOperation with a completionBlock for requests to Amazon REST API. The client is able to monitor the status of all scheduled requests via a property on the main class called isBusy. In my operation's completion block I set the value for this property to NO if there are no other operations scheduled in my NSOperationQueue. I now figured out that in some rare cases my current NSOperation is still included in my NSOperationQueue when the completionBlock gets called. This seems a little strange to me. I ended up checking for existence of the current NSOperation in my queue to fix this, but this seems unnecessary from a design point of view.
__weak AWSOperation* thisOperation = operation;
[operation setCompletionBlock:^{
if (self.operationQueue.operationCount <= 0
|| (self.operationQueue.operationCount == 1
&& [self.operationQueue.operations objectAtIndex:0] == thisOperation)) {
[self willChangeValueForKey:#"isBusy"];
_isBusy = NO;
[self didChangeValueForKey:#"isBusy"];
}
}];
Does anybody have more information on this behavior?
From the docs:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context.
So, you can't guarantee that it will be called before or after the operation is removed from the queue. It's sent after the trigger to remove the operation from the queue (because the operation is finished) but there is effectively a race between those 2 things.
Your best option is to push your consideration to the next iteration of the main runloop if you're interrogating the operation queue itself to determine the state.
First I create a serial queue like this
static dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
then, at some unknown point in time a task gets added to the queue like this
dispatch_async(queue, ^{
// do something, which takes some time
});
If the first task hasn't finished yet, the new task will wait until the first completes (that's of course what a serial queue is for).
But if I add 5 new tasks to the queue, while the original first one is still running, I don't want to execute new task no.1, then no.2, then no.3 and so on, but want to get rid of tasks 1 to 4 and directly start executing task no.5 after the original first task has finished.
In other words, I want to pop any waiting task (not the one that is currently running) off the queue, if I add a new one.
Is there a build in mechanism for this or do I have to implement this myself? And for the latter, how would I identify single tasks inside a queue and remove them?
Once a block has been submitted to a GCD dispatch queue, it will run. There is no way to cancel it. You can, as you know, implement your own mechanism to "abort" the block execution early.
An easier way to do this would be to use NSOperationQueue, as it already provides an implementation for canceling pending operations (i.e., those not yet running), and you can easily enqueue a block with the new-ish addOperationWithBlock method.
Though NSOperationQueue is implemented using GCD, I find GCD much easier to use in most cases. However, in this case, I would seriously consider using NSOperationQueue because it already handles canceling pending operations.
With Davids answer getting me on track I succeeded in doing this like so
taskCounter++;
dispatch_async(queue, ^{
if (taskCounter > 1) {
taskCounter--;
NSLog(#"%#", #"skip");
return;
}
NSLog(#"%#", #"start");
// do stuff
sleep(3);
taskCounter--;
NSLog(#"%#", #"done");
});
taskCounter has to be either an ivar or a property (initialize it with 0). In that case it doesn't even need the __block attribute.
The way you handle this is to use an ivar that indicates to the queued blocks they should just return:
^{
if(!canceled) {
... do work
}
}
You don't need to use a simple boolean either - you can make this more complex - but the general idea is to use one or more ivars that the block queries before doing anything.
I use this technique (but did not invent it) with great success.
If instead of adding a closure in you add a DispatchWorkItem, you can cancel it as long as it hasn't started executing yet.
In the following code, backgroundWorkItem will never run, because it is cancelled before it starts executing.
let backgroundWorkItem = DispatchWorkItem {
print("Background work item executed")
}
DispatchQueue.main.async(execute: backgroundWorkItem)
backgroundWorkItem.cancel()
I'm executing the following method :
MotionHandler.m
-(void)startAccelerationUpdates
{
[motionManagerstartDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]withHandler:^(CMDeviceMotion *motion, NSError *error){.....}
}
on a background thread, as follows:
[currentMotionHandler performSelectorInBackground:#selector(startAccelerationUpdates) withObject:nil];
But the above method uses the main Queue (which is on the main thread) to perform the necessary updates even though I'm calling it on a background thread.. So are acceleration updates being performed on a background thread or on the main thread, I'm confused..?
What's even more interesting is that when I call the above method on background thread again, but this time using the current Queue, I get no updates. Could someone please explain the difference between running something on :
1. a background thread but on the main queue
2. a background thread but on the current queue
3. the main thread but on the main queue
4. the main thread but on the current queue
in the current implementation? Thank you!
I'll give it a shot. First, without being told by the NSOperationQueue class reference, we could not infer anything about what thread the 'mainQueue' would run on. Reading it we see that in fact that queue runs its operations on the mainThread, the one the UI uses, so you can update the UI in operations posted to that queue. Although it doesn't say it, these operations must be serial, due to them being executed by the runLoop (its possible they can get preempted too, not 100% sure of that).
The purpose for currentQueue is so that running operations can determine the queue they are on, and so they can potentially queue new operations on that queue.
a background thread but on the main queue
Not possible - the NSOperation's mainQueue is always associated with the mainThread.
a background thread but on the current queue
When you create a NSOperationQueue, and add NSOperations to it, those get run on background threads managed by the queue. Any given operation can query what thread its on, and that thread won't change while it runs. That said, a second operation on that queue may get run on a different thread.
the main thread but on the main queue
See 1)
the main thread but on the current queue
If you queue an operation to the mainQueue (which we know is always on the mainThread), and you ask for the currentQueue, it will return the mainQueue:
[NSOperationQueue currentQueue] == [NSOperationQueue mainQueue];
You are confusing queues with threads. Especially since NSOpertionQueue has been rewritten to use GCD, there is little connection between queues and specific threads (except for the special case of the main thread).
Operations/blocks/tasks - whatever you want to call them - are inserted into a queue, and "worker thread(s)" pull these off and perform them. You have little control over which exact thread is going to do the work -- except for the main queue. Note, this is not exactly right, because it's a simplification, but it's true enough unless you are doing something quite advanced and specific.
So, none of your 4 scenarios even make sense, because you can't, for example, run something on "a background thread but on the main queue."
Now, your method startAccelerationUpdates specifically tells the CMMotionManager to put your handler on the main queue. Thus, when startAccelerationUpdates is called, it gets run in whichever thread it's running, but it schedules the handler to be executed on the main thread.
To somewhat complicate things, you are calling the startAccelerationUpdates method by calling performSelectorInBackground. Again, you don't know which thread is going to actually invoke startAccelerationUpdates, but it will not be the main thread.
However, in your case, all that thread is doing is calling startAccelerationUpdates which is starting motion updates, and telling them to be handled on the main thread (via the main queue).
Now, here's something to dissuade you from using the main queue to handle motion events, directly from the documentation...
Because the processed events might arrive at a high rate, using the main operation queue is not recommended.
Unfortunately, your statement
What's even more interesting is that when I call the above method on
background thread again, but this time using the current Queue, I get
no updates.
does not provide enough information to determine what you tried, how you tried it, or why you think it did not work. So, I'll make a guess... which may be wrong.
I'll key on your use of the current Queue.
I assume you mean that you substitute [NSOperationQueue mainQueue] with [NSOperationQueue currentQueue].
Well, let's see what that does. Instead of using the main queue, you will be using "some other" queue. Which one? Well, let's look at the documentation:
currentQueue
Returns the operation queue that launched the current
operation.
+ (id)currentQueue
Return Value
The operation queue that started the operation or nil if the queue could not be determined.
Discussion
You can use this method from within a running operation
object to get a reference to the operation queue that started it.
Calling this method from outside the context of a running operation
typically results in nil being returned.
Please note the discussion section. If you call this when you are not running an operation that was invoked from an NSOperationQueue, you will get nil which means there will be no queue on which to place your handler. So, you will get nothing.
You must specify which queue is to be used, if you want to use an NSOperationQueue other than the main queue. So, if that's the route you want to go, just create your own operation queue to handle motion events, and be off!
Good Luck!
Let's say I have a serial dispatch queue and I enqueue several operations on it. I've read that I can't cancel operations once they are dispatched. Is it possible to at the very least view what GCD blocks I've dispatched to maybe make a decision if I want to dispatch another one?
Example, I dispatch Operation A to the queue but soon after my application decides to enqueue another Operation A, so now there are 2 of these operations queued up.
As Kevin Ballard said, you need to elaborate on what exactly you are trying to do. One thing you could do is set a flag, like valid_ and then you can effectively cancel all but the current item in the queue by doing something like this:
dispatch_async(queue, ^{
if (valid_) {
// perform your task here
}
});
Then whenever you want to "cancel" the queue, just set your valid_ flag to NO.
Again though, give more info on what you are trying to do and I can give you a better answer.
Since NSOperation is now built on top of GCD, you can now use addOperationWithBlock: to put your block on an NSOperationQueue, then you can invoke operations on the NSOperationQueue to get an NSArray of unfinished operations.
The problem with this, is that this is more than two operations and is not atomic, so it's entirely possible that your operation will finish in between the time you get the operations array and see if it's contained there.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html
NSOperations have a prerequisite API, however, so you can enqueue another operation which will only run if your first NSOperation finishes, and use this to keep track of when you should try to enqueue your first NSOperation again.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html#//apple_ref/doc/uid/TP40004591
Working with some code, I'm coming across run loops, which I'm new to, inside NSOperations.
The NSOperations are busy downloading data - and whilst they are busy, there is code to wait for the downloads to complete, in the form of NSRunLoops and thread sleeping.
This code in particular is of interest to me:
while (aCertainConditionIsTrue && [self isCancelled]==NO) {
if(![[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]){
[NSThread sleepForTimeInterval:1.0];
}
}
I've read about the run loops, and runMode:beforeDate: will wait for an input source or a timeout. Although I'm not 100% what counts as an input souce.
On the first execution of this it always returns NO and hits the sleepForTimeInterval:. Is this bad?
In a particular utility class, it's hitting the sleepForTimeInterval: a lot - once for each thread - which significantly hurts the performance.
Any better solutions for this, or advice?
Sleeping locks up the thread. Perhaps you change your code to use performSelector:withObject:afterDelay. That way your thread can continue to run.
...
done = NO;
[self checkDoneCondition:nil];
...
- (void)checkDoneCondition:(id)object {
if (aCertainConditionIsTrue && [self isCancelled]==NO) {
if(...) {
[self performSelector:#selector(checkDoneCondition:) withObject:[con error] afterDelay:1.0];
} else {
done = YES;
}
}
}
It looks like you need to use a concurrent NSOperation. Here is the relevant part in the Apple docs:
In contrast to a non-concurrent operation, which runs synchronously, a
concurrent operation runs asynchronously. In other words, when you
call the start method of a concurrent operation, that method could
return before the corresponding task is completed. This might happen
because the operation object created a new thread to execute the task
or because the operation called an asynchronous function. It does not
actually matter if the operation is ongoing when control returns to
the caller, only that it could be ongoing.
(...)
In a concurrent operation, your start method is responsible for
starting the operation in an asynchronous manner. Whether you spawn a
thread or call an asynchronous function, you do it from this method.
Upon starting the operation, your start method should also update the
execution state of the operation as reported by the isExecuting
method. You do this by sending out KVO notifications for the
isExecuting key path, which lets interested clients know that the
operation is now running. Your isExecuting method must also return the
status in a thread-safe manner.
(from https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html)
In other words, you can override the -start method in your NSOperation subclass, and have ivar for the executing and finished property. This method will start the download in a separate thread. When the download starts, you set the executing flag and trigger KVO. WHen it is finished in this thread, you do the same with finished and executing. It seems complicated but it's actually quite simple.
See also this question on Stack Overflow with a great explanation: Subclassing NSOperation to be concurrent and cancellable