4

I am writing an application which will do a multiple task simultaneously. One particular task is to do a job once every 200 ms. To achieve this, I am using two methods calling each other. The first method just calls the second and the second method calls the first with a delay using dispatch_after().

After a few iterations(300-400 times) the block in dispatch_after is not executed after 200 ms. It takes ~5-10 seconds before the block is executed. Please let me know the reason for the behaviour(delay). I also tried NSThread (sleepForTimeInterval:) and I am facing the same problem there too.I am stuck. Please help me.

The code is given below.

Screen.h

#import <Foundation/Foundation.h>

@interface Screen : NSObject

-(void) firstMethod;
-(void) secondMethod;
@end

Screen.m

#import "Screen.h"

@implementation Screen

int i=0;
dispatch_queue_t another_queue;
dispatch_time_t pop_time;

-(Screen*) init {
    self = [super init];
    if (self) {
        another_queue = dispatch_queue_create("com.test.timer.2", NULL);
    }
    return self;
}

-(void) firstMethod {
    i++;
    NSLog(@"i value : %d",i);
    [self secondMethod];
}

-(void) secondMethod {
    pop_time = dispatch_time(DISPATCH_TIME_NOW, 200 * NSEC_PER_MSEC);
    dispatch_after(pop_time, another_queue, ^(void){
        [self firstMethod];
    });
}

@end

AppDelegate.h

#import <Cocoa/Cocoa.h>
#import "Screen.h"

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;

@end

AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application
    Screen* screen = [[Screen alloc] init];
    dispatch_queue_t first_queue = dispatch_queue_create("com.test.timer", NULL);
    dispatch_block_t blk =^(void) {
        [screen firstMethod];
    };
    dispatch_async(first_queue, blk);
}

@end
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • usually the reason this happens, is that whatever you are doing takes too long, so the next time that the runloop spins more time has elapsed. – Grady Player Mar 04 '14 at 06:33
  • @GradyPlayer, To check the behaviour, I simplified the code and posted above. In the above code, I am just incrementing a variable and printing it. I assume this shouldn't take much time. If my understanding is wrong, can you please throw more light on this. – Senthil Ganesh Mar 04 '14 at 06:38
  • @SenthilGanesh, are simplified code have same behavior? Because it's looks like you call fistMethod or secondMethod separately second time not in yours 200ms loop. – Cy-4AH Mar 04 '14 at 06:41
  • @Cy-4AH, I am facing the problem in above code. I am sorry, I don't understand what you mean. Can you please elaborate? – Senthil Ganesh Mar 04 '14 at 07:05
  • The issue is not related to "run loops". Something else is happening. – CouchDeveloper Mar 04 '14 at 08:07

3 Answers3

6

Is your app in the foreground when this occurs? If not, you may simply be seeing App Nap kicking in. One of the things it does is throttle timers in background apps.

Catfish_Man
  • 41,261
  • 11
  • 67
  • 84
  • Would App Nap delay the timers up to 5-10 seconds (as described in the question)? – Martin R Mar 04 '14 at 08:17
  • @MartinR Unfortunately, the details are not revealed in the documentation. Possibly, yes. Perhaps, not. One needs to investigate the issue. – CouchDeveloper Mar 04 '14 at 08:19
  • @MartinR I noticed that a dispatch source timer that I was playing around with in response to this question (i.e. `DISPATCH_SOURCE_TYPE_TIMER`) was experiencing up to 10 second delays when the app was running in background, and when I programmatically disabled app nap (with `beginActivityWithOptions`), those 10 second delays seemed to go away. – Rob Mar 04 '14 at 08:25
  • Indeed, it really can be 10 seconds! Thanks for the info! – Martin R Mar 04 '14 at 08:38
  • Thank you @Catfish_Man and CouchDeveloper. You are right. If the app is continuously running in the foreground, the application runs smoothly without any delay. Whenever the "extended" delay occurs, bringing the app to the foreground solves the problem. Thank you so much. I had my head buried in my code for many days without knowing about "App Nap". – Senthil Ganesh Mar 04 '14 at 09:07
  • @SenthilGanesh Actually, if the app is in foreground and when you use dispatch_after() you will experience the "leeway" which is up to 10% of the specified delay. dispatch_after uses this leeway internally, which can be examined in the sources. For example, when you set a delay of 1.0 sec, then you may actually get delays from 1.000 to 1.100 secs. When using a dispatch timer source, one can explicitly specify this "leeway" value. I wouldn't bother though, unless you need a _precise_ timer. – CouchDeveloper Mar 04 '14 at 10:44
  • @SenthilGanesh @Rob; @Catfish, I don't think you are correct. I can't reproduce this behavior (on a device) when the app is in background (no Backgroundmode), though: immediately after entering background, `dispatch_after` and `dispatch timer source` simply stop executing their blocks completely (well, longer than minutes), and they continue with executing when the app goes t foreground. – CouchDeveloper Mar 04 '14 at 11:01
  • @CouchDeveloper, In my case(in the above program), the delay is not just 10% more. 200ms delay is stretched to 10 seconds. – Senthil Ganesh Mar 04 '14 at 11:25
  • @CouchDeveloper, I am developing OSX app and I am using MacBook Pro with OSX 10.9. In my MacBook Pro, the delay is 10 seconds when the app runs in background. – Senthil Ganesh Mar 04 '14 at 11:30
  • @SenthilGanesh Uhps, I got confused by iOS vs OS X. Regarding background mode, my observations are for iOS. However, the "leeway" in dispatch_after behaves exactly the same in iOS and Mac OS X. I didn't test in Mac OS X and background. I wouldn't have expected such a huge delay, though. That's definitely alarming. Sometimes, computers should do something, and not sleeping ;) – CouchDeveloper Mar 04 '14 at 11:36
  • 1
    App Nap definitely delays up to 10 seconds. If you're doing things in the background for the user, you need to mark them as such using the NSProcessInfo APIs. If it's work that's not directly important to the user, then allowing it to be throttled is good. – Catfish_Man Mar 04 '14 at 17:49
1

One possible effect is "Timer Coalescing" and "App Nap".

Related: this question: Have you noticed that dispatch_after runs ~10% too slow on iOS devices?.

If this is actually the cause of your problem, you can fix it using a timer, either NSTimer, or use our own implementation, based on dispatch lib, where you can control the exact behavior. See also: an implementation of a timer on Gist: RXTimer

Edit:

On Mac OS X and when App Nap kicks in, it seems we have no control over the delay respectively that huge "leeway".

Community
  • 1
  • 1
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • I'm not sure using `NSTimer` (or GCD dispatch timer source) will fix this problem, as my testing of both suggests that the delay can still occur. I remedied by calling `[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiated reason:@"..."];` – Rob Mar 04 '14 at 08:33
  • Thank you @CouchDeveloper, Rob and Martin R for your help!. I have disabled App Nap using if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:reason:)]) { self.activity = [[NSProcessInfo processInfo] beginActivityWithOptions:0x00FFFFFF reason:@"For disabling App Nap"]; } – Senthil Ganesh Mar 04 '14 at 10:05
  • @Rob @SenthilGanesh I've confirmed that `dispatch_source_set_timer()` is accurate as expected with regard to the leeway value which can be explicitly specified (on a device on iOS 7.0.x). The linked source for RXTimer uses `dispatch_source_set_timer`. (When the delay is set to 1.0 seconds, the actual average time measured was 1001.2 ms. `dispatch_after` uses internally a default leeway of 10% of the delay, e.g. getting up to 1100 ms) – CouchDeveloper Mar 04 '14 at 10:24
  • @CouchDeveloper Hmm. I can't reconcile that with my tests of dispatch timer source, which despite the setting of the appropriate leeway value, if App Nap kicked in, it would intermittently affect the the interval by up to 10 seconds that the OP referenced. Once App Nap was disabled, it then behaved as desired. – Rob Mar 04 '14 at 13:05
  • @Rob Yes, I was wrong here for Mac OS X and wenn App Nap kicks in, I apologize. Any hints where this is possibly documented? – CouchDeveloper Mar 04 '14 at 13:43
  • @CouchDeveloper The reference I found was WWDC 2013 video [#205 - What’s New in Cocoa](https://developer.apple.com/wwdc/videos/?include=205#205) (roughly half way in), but it doesn't provide any specifics about when it kicks in, only how to use the Activity APIs to declare activities that should be exempted from App Nap. – Rob Mar 04 '14 at 14:30
-1

I had the same issue on OS X. My delay was more than 1000% off of the specified delay.

Turning off App Nap system wide solved the leeway:

defaults write NSGlobalDomain NSAppSleepDisabled -bool YES
Klaas
  • 22,394
  • 11
  • 96
  • 107