NSOperationQueue and NSDispatchQueue as replacement of NSThread performing a repetitive task

155 views
Skip to first unread message

Decoder

unread,
Jan 5, 2012, 10:30:54 AM1/5/12
to cocoa-unbound
Hi All,

I have an application in which I am repetitively calling a method in
background. I implemented this by following below steps:

1. created a background thread,
2. called the appropriate method on the created thread,
3. called sleep method on the thread, and
4. again called the previously invoked method.

Below is the code which I used:

- (void) applicationDidFinishLaunching:(NSNotification *)notification
[NSApplication
detachDrawingThread:@selector(refreshUserIdPassword) toTarget:self
withObject:nil];
}

-(void)refreshUserIdPassword
{
[self getAllUserIdsPasswordsContinousely];
[NSThread sleepForTimeInterval:180];
[self refreshUserIdPassword];

}

I have read that NSThread is not the best way to perform background
task, and there are other classes provided in cocoa, such as -
NSOperationQueue and NSDispatchQueue, which should be preferred over
NSThread to perform an asynchronous task. So I am trying to implement
the above specified functionality using the alternative classes.

Problem is - though I am able to perform an asynchronous task using
these classes, I am unable to perform a repetitive task (as in my
case) using these classes.

Also I know that I can use NSTimer but I wanted to know if same
functionality can be achieved through NSOperationQueue and
NSDispatchQueue and if yes then how?

Can someone throw some light on this and guide me towards the correct
direction?

Thanks,

Michael Ash

unread,
Jan 5, 2012, 10:44:38 AM1/5/12
to cocoa-...@googlegroups.com
On Jan 5, 2012, at 10:30 AM, Decoder wrote:

> Problem is - though I am able to perform an asynchronous task using
> these classes, I am unable to perform a repetitive task (as in my
> case) using these classes.
>
> Also I know that I can use NSTimer but I wanted to know if same
> functionality can be achieved through NSOperationQueue and
> NSDispatchQueue and if yes then how?
>
> Can someone throw some light on this and guide me towards the correct
> direction?

I'd recommend dropping down one more level and using GCD. It's a little uglier than NSOperationQueue, but really not much. The API is pretty usable, and *much* more powerful.

For your particular use, a dispatch timer will do what you need. You can create one using dispatch_source_create and the DISPATCH_SOURCE_TYPE_TIMER type. Target it at one of the GCD global queues and you have a periodic task that runs in a background thread without much work on your part.

For more information on dispatch timers, I have a bit of discussion on them near the end of this article:

http://mikeash.com/pyblog/friday-qa-2009-09-11-intro-to-grand-central-dispatch-part-iii-dispatch-sources.html

Mike

Jim Dovey

unread,
Jan 5, 2012, 11:00:37 AM1/5/12
to cocoa-...@googlegroups.com
Well, firstly there's a problem here:

On 2012-01-05, at 10:30 AM, Decoder wrote:

> -(void)refreshUserIdPassword
> {
> [self getAllUserIdsPasswordsContinousely];
> [NSThread sleepForTimeInterval:180];
> [self refreshUserIdPassword];
> }

Aside from mis-spelling 'continuously' (sorry, can't help it), this is calling itself every three minutes, so the stack will keep growing infinitely. It'll take a long time, sure, but it's still a Bad Thing.

> I have read that NSThread is not the best way to perform background
> task, and there are other classes provided in cocoa, such as -
> NSOperationQueue and NSDispatchQueue, which should be preferred over
> NSThread to perform an asynchronous task. So I am trying to implement
> the above specified functionality using the alternative classes.

I believe you mean dispatch_queue_t? There's no such class as NSDispatchQueue that I can find, and NSOperationQueue is already built on top of dispatch queues, which are a C-level construct.

> Problem is - though I am able to perform an asynchronous task using
> these classes, I am unable to perform a repetitive task (as in my
> case) using these classes.

Not entirely on their own, no— they're designed for enqueueing discrete jobs, not for performing something on a schedule.

> Also I know that I can use NSTimer but I wanted to know if same
> functionality can be achieved through NSOperationQueue and
> NSDispatchQueue and if yes then how?

Yes, you should be using NSTimer, like this:

...
// run the method right now for the first time...
[self getAllUserIdsPasswordsInBackground];

// ...then set up a timer. It will fire for the first time in 180 seconds (three minutes), and every three minutes thereafter
self.passwordTimer = [NSTimer scheduledTimerWithTimeInterval: 180.0 target: self selector: @selector(getAllUserIdsPasswordsInBackground) userInfo: nil repeats: YES];
...

- (void) getAllUserIdsPasswordsInBackground
{
[self performSelectorInBackground: @selector(getAllUserIdsPasswordsContinuously)];
}

When you're done with the timer, do:

[self.passwordTimer invalidate];
self.passwordTimer = nil; // assuming you're using GC or ARC. Otherwise you need to -release the timer too.

You can also use a dispatch timer source to have everything run in the background automatically, but this is a bit more involved than the above:

// create the timer:
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));

// setup an event handler which will run on the target queue (global default-priority queue was specified at creation above).
dispatch_source_set_event_handler(timer, ^{
[self getAllUserIdsPasswordsContinuously];
});

// dispatch sources are created in a 'suspended' state, so you must 'resume' them to start them off
dispatch_resume(timer);

That looks simpler though, no? Why did I say it was involved?

Because the block password to dispatch_source_event_handler() is using 'self', which causes 'self' to be automatically retained along with the block. This means that, unless you manually dispatch_release() the timer, your object's -dealloc method will never be called. Therefore, you can't rely on being able to release the timer in your object's -dealloc method. The same is NOT true of NSTimers-- they don't retain their targets, thus you MUST -invalidate any NSTimers in your -dealloc method.

Now, if you're using ARC or garbage collection, you needn't worry about keeping track of the NSTimer-- if it's a member variable (or a property) then the system will arrange for it to be released at the appropriate time. However, the same is NOT true of the dispatch timer-- dispatch objects are not Objective-C objects, so you must still implement -dealloc under ARC (or -finalize under GC) in order to release it, which still results in a retain loop. Dispatch objects are neither automatically reference-counted nor garbage-collected, although *blocks* are, which is from where the problem ultimately arises.

However: you CAN use the dispatch timer approach so long as the block doesn't reference 'self' at all. If the -getAllUserIdsPasswordsContinuously function simply calls some external function, then you can just call that external function from your timer's event-handler block and you'll be fine, i.e.

dispatch_source_set_event_handler(timer, ^{
// doesn't reference 'self', and so does not cause a retain cycle.
[[MyPasswordHandler sharedInstance] updateUserIdsAndPasswordsNow];
});

My advice: use an NSTimer. Unless it's ABSOLUTELY ESSENTIAL that this method be called EXACTLY every 180 seconds on a predefined schedule (NSTimer will *count* 180 seconds of live time after each fire invocation is complete), that's your simplest and best option. If you really need a real-time timer, then you likely want something significantly more complicated again. So just stick with NSTimer.

Chris Suter

unread,
Jan 6, 2012, 3:50:14 PM1/6/12
to cocoa-...@googlegroups.com
Hi Jim,

This is off-list because I don't want to add to the noise and it's not
important.

On Fri, Jan 6, 2012 at 3:00 AM, Jim Dovey <jimd...@gmail.com> wrote:

> Aside from mis-spelling 'continuously' (sorry, can't help it), this is calling itself every three minutes, so the stack will keep growing infinitely. It'll take a long time, sure, but it's still a Bad Thing.

Actually, I doubt it will. I believe the compiler will use tail call
recursion. Still, I wouldn't recommend relying on it, not least
because other developers looking at the code will think (like you did)
that there's a problem with it.

Kind regards,

Chris

Chris Suter

unread,
Jan 6, 2012, 3:51:20 PM1/6/12
to cocoa-...@googlegroups.com
On Sat, Jan 7, 2012 at 7:50 AM, Chris Suter <csu...@sutes.co.uk> wrote:

> This is off-list because I don't want to add to the noise and it's not
> important.

Ha! Sorry. I meant to send it off-list.

Kind regards,

Chris

Reply all
Reply to author
Forward
0 new messages