NSOperationQueue serial FIFO queue
Asked Answered
L

3

17

Is it possible to use an NSoperationQueue object as a serial FIFO queue by setting its maxConcurrentOperationCount to 1?

I note that the docs state...

For a queue whose maximum number of concurrent operations is set to 1, this equates to a serial queue. However, you should never rely on the serial execution of operation objects.

Does this mean that FIFO execution is not guaranteed?

Lanza answered 8/6, 2012 at 12:24 Comment(0)
R
25

In most cases, it will be FIFO. However, you can set up dependencies between NSOperations such that an operation submitted early will let other operations pass it by in the queue until its dependencies are satisfied.

This dependency management is why the docs indicate that FIFO-ness cannot be guaranteed. If you're not using dependencies, though, you should be fine to rely on it.

Update: NSOperation also has a queuePriority property, which can also cause operations to execute in non-FIFO order. The highest-priority operation with no pending dependencies will always execute first.

An NSOperation subclass might also override -isReady, which could cause it to move back in the queue.

So execution on your queue is guaranteed to be serial, in that no more than one operation will run at a time in this queue. But Apple can't guarantee FIFO; that depends on what you're doing with the operations you put in.

Rearrange answered 8/6, 2012 at 12:30 Comment(8)
Order of execution also depends on the operation's queue priority.Anastomose
So if my operations all have the same priority, have no dependencies and rely only on the superclass implementation of isReady (I don't override) it should result in a FIFO queue?Lanza
Correct; NSOperationQueue only reorders things if you give it a reason to do so. If you don't do any of those things, it just takes them in order.Rearrange
What about if I have a bunch of operation that I want to execute at mainqueue serially? Setting the maxConcurrentOperationCount to 1 of mainqueue may cause choppy interface.Amide
The main queue is tied to a single thread, and as such can inherently only run one operation at a time. So it's maxConcurrentOperationCount is already 1. Setting it to 1 again won't cause any problems.Rearrange
Is it normal to get a new thread for each operation added to the queue (I could have 100's of them), most waiting at a semaphore? Seems an inefficient way to do a serial queue.Palmapalmaceous
is this you BJ Homer? atsloginc.com/nsoperationqueue-serial-fifo-queue. It sure looks like more code and similar.Friday
Nope. Looks like it's just a copy of the questions and answers on this page.Rearrange
H
14

The queue is not FIFO as mentioned by the documentation. You can make it strictly FIFO if you ensure that any new operation depends on the last operation added in the queue and that it can only run a single operation at a time. Omar solution is correct, but more generally, you can do the following:

NSOperationQueue* queue = [[ NSOperationQueue alloc ] init];
queue.maxConcurrentOperationCount = 1;

NSOperation* someOperation = [ NSBlockOperation blockOperationWithBlock:^(void) { NSLog(@"Done.");} ];

if ( queue.operations.count != 0 )
    [ someOperation addDependency: queue.operations.lastObject ];

This works because queue.operations is an array: whatever you add is not reordered (it is not a NSSet for instance). You can also simply add a category to your NSOperationQueue:

@interface NSOperationQueue (FIFOQueue)
- (void) addOperationAfterLast:(NSOperation *)op;
@end

@implementation NSOperationQueue (FIFOQueue)

- (void) addOperationAfterLast:(NSOperation *)op
{
    if ( self.maxConcurrentOperationCount != 1)
        self.maxConcurrentOperationCount = 1;

    NSOperation* lastOp = self.operations.lastObject;
    if ( lastOp != nil )
        [ op addDependency: lastOp ];

    [ self addOperation:op];
}

@end

and use [queue addOperationAfterLast:myOperation]. queuePriority has nothing to do with FIFO, it is related to job scheduling.

Edit: following a comment below, suspending the queue if checking for the count is also not sufficient. I believe this form is fine (upon testing, this does not create a race condition and does not crash).

Some info: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperationQueue_class/#//apple_ref/occ/instp/NSOperationQueue/suspended

Hayott answered 21/9, 2015 at 18:1 Comment(5)
There is a problem with this type of solution. I've been using it. Occasionally, the queue's lastObject will disappear after the check on queue.operations.count. It's rare, but it happens. And I'm not sure how to solve it.Everything
Good point I did not see that but yes: if operations.count is checked while the queue runs, it may end while we are entering the if condition. I believe the edited form is better and safer : (tests here do not crash, while ay attempt to suspend the queue does not work (not shown)).Hayott
That's the solution I came to too; create a local variable, preventing the NSOperation from being released.Everything
suspend the queue while you are adding all the operations.Meperidine
@Meperidine Would that work? I thought that when you suspend an operation queue it will not start any new operations but any operations that are executing will continue. If this is the case then suspending the queue wouldn't prevent the last operation from completing after the queue.operations.count check? So the problem would remain.Aport
P
-3

To make a simple FIFO using nsInvocationopration You will need to set one operation to be depended on the other Using addDependency: method

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *oper1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSth:) object:@"1"];

NSInvocationOperation *oper2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSth:) object:@"2"];
NSInvocationOperation *oper3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSth:) object:@"3"];

[oper2 addDependency:oper1];
[oper3 addDependency:oper2];
//oper3 depends on oper2 wich depends on oper1
//order of execution will ber oper1->oper2->oper3

//Changing the oreder will not change the result
[queue addOperation:oper2];
[queue addOperation:oper3];
[queue addOperation:oper1];


- (void) doSth:(NSString*)str
{
    NSLog(str); //log will be 1 2 3
    //When you remove the addDependency calls, the logging result that i got where
    //different between consecutive runs i got the following
    //NSLog(str); //log will be 2 1 3
    //NSLog(str); //log will be 3 1 2
}

Note: if you are using NSInvocationOperation then setting the maxConcurrentOperationCount to 1 would most likely do the trick to you, since isReady will not be editable by you

But maxConcurrentOperationCount = 1 would not be a good solution if you are planing to create your own subclasses of NSOperation

Since in NSOperation derivatives you could override the isReady function and return no, (imagine some operation that would need to wait some data from a server to in order to function properly) in these cases you would return isReady no until you are really ready In these cases you will need to add dependencies between operations inside the queue

From apple docs this equates to a serial queue. However, you should never rely on the serial execution of operation objects. Changes in the readiness of an operation can change the resulting execution order

Prioress answered 8/6, 2012 at 12:31 Comment(5)
Can you explain why the dependencies are necessary?Rearrange
Your queue in the example does not have maxConcurrentOperationCount = 1 like the OP specified. Does this still happen with that set?Rearrange
In this example it wont, but consider a rather bigger operation where you would need to subclass NSOperation and override isReady, and your implementation of isReady may return no, consider the following comment in the apple docs this equates to a serial queue. However, you should never rely on the serial execution of operation objects. Changes in the readiness of an operation can change the resulting execution orderPrioress
Okay, that's a valid example. But your original answer is totally invalid; NSInvocationOperation doesn't do anything itself that would cause non-FIFO execution order.Rearrange
yes, you are right, my example was bad indeed, maybe i shall edit it and add the some more explanaion :)Prioress

© 2022 - 2024 — McMap. All rights reserved.