5

I am adding operations to the queue using something like this

NSInvocationOperation *operation0 = [[NSInvocationOperation alloc] 
initWithTarget:self
selector:@selector(doStuff1) 
object:nil];

[queue addOperation:operation0];
[operation0 release];   


NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] 
initWithTarget:self
selector:@selector(doStuff2) 
object:nil];

[queue addOperation:operation1];
[operation1 release];   


NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] 
initWithTarget:self
selector:@selector(doStuff3) 
object:nil];

[queue addOperation:operation2];
[operation2 release];   

The queue is set to do just one operation at a time. So it will run the 3 methods without delay, one after another. Is that a way to add a small delay, lets say 0.5 seconds or whatever between each operation, so instead of running

doStuff1
doStuff2
doStuff3

the queue would do

doStuff1
sleep for X seconds
doStuff2
sleep for X seconds
doStuff3

?

thanks.

Duck
  • 34,902
  • 47
  • 248
  • 470
  • What about adding an operation that has the sleep on it in between the other operations? – Jorge Ferreira Dec 13 '10 at 11:54
  • what operation is that? this is the question... :) – Duck Dec 13 '10 at 11:56
  • Create an NSInvocationOperation as you did but the method executed runs a `sleep`. – Jorge Ferreira Dec 13 '10 at 12:00
  • Thanks, but do you care to transform your comments in an answer and put some code there, so I can happily award the answer to you? Generic answers are not helping... I need some code to see... please...thanks. – Duck Dec 13 '10 at 12:04
  • What exactly are you trying to do? I lack the imagination to think of a scenario where that's necessary. Maybe there is a nicer way to get the same result without hacking NSOperation? – Costique Dec 13 '10 at 12:05
  • I am exactly trying to run N methods in sequence with a delay between them. A method can only run when the previous one finishes. There must be a delay between them. Methods should run on a separate thread (they are heavy enough to hang the main interface). – Duck Dec 13 '10 at 12:14

4 Answers4

12

Using sleep is a big waste of a thread. This is bad in the Ghostbusters' sense.

If you need a queue that executes its operations non-concurrently i.e. sequentially you can simpy set its maxConcurrentOperationCount variable to 1.

queue.maxConcurrentOperationCount = 1;

To pause between operations you could do two different things:

a) Delay the queueing of each operation:

//loop over the operations to be queued with and index
      double delayInSeconds = 2.0 * index;
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            queue.addOperation(operation at index); 
        });

This will queue all operations without blocking.

b) Every operation has to implement

- (BOOL)isFinished

as you probably already know.

If your operation is "done" just delay setting its "done" variable by your desired delay. Use something like:

// Operation is done with its work, but we tell the Queue only after 2 seconds, so the queue will start the next operation later
double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
 __weak __typeof(self)weakSelf = self;
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf willChangeValueForKey:@"isExecuting"];
    [weakSelf willChangeValueForKey:@"isFinished"];
    weakSelf.executing = NO;
    weakSelf.finished  = YES;
    [weakSelf didChangeValueForKey:@"isFinished"];
    [weakSelf didChangeValueForKey:@"isExecuting"];
    });

Variant a) guarantees a pause between starting times of operations. Variant b) guarantees a pause between the end of an operation and the start of a next operation.

MacMark
  • 6,239
  • 2
  • 36
  • 41
  • option b is definitely the cleanest of any solution here and doesn't involve needlessly blocking a queue. Worked great for me, thanks a bunch! If this feels like too much setup compared to the other solutions, I'd recommend looking at something like PSOperations (https://github.com/pluralsight/psoperations) that wraps up most of the KVO for you – Marcus Mar 30 '16 at 23:06
  • This should be the correct answer, using sleep has unintended consequences unless you have created your own thread. – Duncan Groenewald Jun 30 '17 at 13:03
5

Without extra code, you can't really guarantee that your NSOperations will run in serial - if Apple release a multiprocessor iOS device, it will run multiple operations in parallel. So firstly, you should set up the dependency chain between your operations. I would suggest that you achieve this using NSOperation's - (void)addDependency:(NSOperation *)operation. You could also roll your own fancy locking code...

You can then add a sleep(5) to the end of each of your doStuff methods. If you need the sleep outside the doStuff methods, create a sleep NSOperation:

- (void)sleepOperationBody; {
sleep(0.5f);
}

NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] 
initWithTarget:self
selector:@selector(sleepOperationBody) 
object:nil];

That said, without knowing in more detail what you need to happen, I think you perhaps just need one NSOperation that combines all three tasks and puts a sleep between them. That would certainly be simpler code.

Rog
  • 17,070
  • 9
  • 50
  • 73
  • 1
    that's it. I will simplify the code. This is just a first draft. But regarding concurrency I thought the command "[queue setMaxConcurrentOperationCount:1];" would be enough to limit the number of parallel executions...thanks. – Duck Dec 13 '10 at 12:43
  • 7
    > Without extra code, you can't really guarantee that your NSOperations will run in serial. Well, yeah you can, right? Just set maxConcurrentOperations on your NSOperationQueue to 1. That would do it, wouldn't it? – Greg Maletic Apr 08 '14 at 23:30
  • Calling sleep() is going to cause everything on the operation queue to stop !! Even if you have concurrent operations they will probably also stop if they are all running on the same thread. So unless you are creating your own thread within the operation you may run into trouble. Well this is my experience of what happens in practice. Please correct me if I am doing something wrong. – Duncan Groenewald Jun 30 '17 at 13:00
2

I have two ways to do such thing.

First: Let the current thread to sleep on the tail of doStuff1, doStuff2, and doStuff3.

Second: Sub-class NSOperation and override the method -(void)main. You can init a custom NSOperation with parameters of target and action like following:

-(id)initWithTarget:(id)target action:(SEL)action;

And execute this action of target in the -(void)main by

[target performSelector:action];
[NSThread sleepForTimeInterval:0.05];
AechoLiu
  • 17,522
  • 9
  • 100
  • 118
1

you can do like following manner

[operation0 addDependancy:operation1];
[operation1 addDependancy:operation2];

and now add [NSThread sleepforTimeInterval:5] to at the end of each task

night bird
  • 67
  • 5