OS X dispatch_source_t在后台几秒钟后挂起

时间:2014-08-25 22:42:24

标签: xcode macos osx-mavericks

我有一个OS X应用程序,它使用调度计时器每秒执行一次任务。简化代码如下所示:

timerQueue2 = dispatch_queue_create("com.mycompany.myappexample_t2", nil);
    d_timer2 = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timerQueue2);
    dispatch_source_set_timer(d_timer2, //the timer
                              dispatch_walltime(NULL, 0), //the start time i guess
                              (1ull * NSEC_PER_SEC), //the interval
                              (1ull * NSEC_PER_SEC) / 1000ull); //the leeway
    dispatch_source_set_event_handler(d_timer2, ^{
        NSLog(@"%f\tT2 click", CACurrentMediaTime());
    });

    dispatch_resume(d_timer2);

它开始很好,每秒记录1次,但在使用任何其他应用程序10-15秒后,计时器将每10秒仅触发一次。我真的不知道是什么原因引起的。

以下是发生错误的摘录:

2014-08-25 18:39:10.967 JB4 App[9679:1003] 66156.545407 T2 click
2014-08-25 18:39:11.967 JB4 App[9679:1003] 66157.545426 T2 click
2014-08-25 18:39:12.967 JB4 App[9679:1003] 66158.545426 T2 click
2014-08-25 18:39:13.967 JB4 App[9679:1003] 66159.545425 T2 click
2014-08-25 18:39:14.967 JB4 App[9679:1003] 66160.545424 T2 click
2014-08-25 18:39:15.967 JB4 App[9679:1003] 66161.545425 T2 click
2014-08-25 18:39:16.967 JB4 App[9679:1003] 66162.545425 T2 click
2014-08-25 18:39:17.966 JB4 App[9679:1003] 66163.544404 T2 click
2014-08-25 18:39:18.967 JB4 App[9679:1003] 66164.545428 T2 click
2014-08-25 18:39:26.031 JB4 App[9679:a30b] 66171.610402 T2 click
2014-08-25 18:39:26.966 JB4 App[9679:a30b] 66172.545741 T2 click
2014-08-25 18:39:27.967 JB4 App[9679:a30b] 66173.545977 T2 click
2014-08-25 18:39:38.966 JB4 App[9679:a30f] 66184.545655 T2 click
2014-08-25 18:39:44.890 JB4 App[9679:a30f] 66190.469197 T2 click
2014-08-25 18:39:46.523 JB4 App[9679:a30f] 66192.102552 T2 click
2014-08-25 18:39:46.967 JB4 App[9679:a30f] 66192.547070 T2 click
2014-08-25 18:39:47.967 JB4 App[9679:a30f] 66193.547071 T2 click
2014-08-25 18:39:48.967 JB4 App[9679:a30f] 66194.546996 T2 click
2014-08-25 18:39:49.967 JB4 App[9679:a30f] 66195.547071 T2 click
2014-08-25 18:40:00.966 JB4 App[9679:a313] 66206.546097 T2 click

更新

似乎DISPATCH_TIMER_STRICT似乎对保持时间准确有所帮助,但现在我找到了别的东西。

除此之外,我还看到nanosleep有时会睡得太久。我想知道这些是否以某种方式相关。

//milliseconds is 100
double pre = CACurrentMediaTime();
struct timespec sleepSpec;
sleepSpec.tv_sec = 0;
sleepSpec.tv_nsec = milliseconds * 1000 * 1000; //ns
nanosleep(&sleepSpec, nil);
double post = CACurrentMediaTime();
if(post-pre > .15)
    NSLog(@"Slept too long");

更新2:

这真的是等待确定时期的最好方式吗?

double pre = CACurrentMediaTime();

while(CACurrentMediaTime() - pre < .1) {
    asm("NOP");
}
double post = CACurrentMediaTime();
if(post-pre > .15)
    NSLog(@"Slept too long");

更新3:

我做了一个简单的测试项目,看看usleep是否真的像我怀疑的那样坏了。要么我完全误解了某些东西,要么这些睡眠功能毫无用处。

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
timerQueue2 = dispatch_queue_create("com.mycompany.myappexample_t2", nil);
d_timer2 = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, timerQueue2);
dispatch_source_set_timer(d_timer2, //the timer
                          dispatch_walltime(NULL, 0), //the start time i guess
                          (1ull * NSEC_PER_SEC), //the interval
                          (1ull * NSEC_PER_SEC) / 1000ull); //the leeway
dispatch_source_set_event_handler(d_timer2, ^{
    //[self timer2Method];
    double time = CACurrentMediaTime();
    if(lastTestTime > 0 && (time - lastTestTime) > 1.1) {
        NSLog(@"Too much delay... %f", time-lastTestTime);
    }
    lastTestTime = time;
    NSLog(@"%f\tClick", lastTestTime);
});

dispatch_resume(d_timer2);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    double lastTime = -1;
    while(1) {
        double thisTime = CACurrentMediaTime();
        NSLog(@"%f", thisTime);
        if(lastTime > 0 && (thisTime-lastTime)>1.1) {
            //0.1 error range
            NSLog(@"Slept too long: %f", thisTime - lastTime);
        }
        lastTime = thisTime;
        usleep(1000000);
    }
});

}

输出日志。正如你所看到的,usleep休息了11秒,而asm(“NOP”)继续正常运行。我知道睡眠不应该是“精确的”,但迟到10秒是不可接受的。

2014-08-25 20:32:14.435 TimingTest[11159:540b] 72940.352926 Click
2014-08-25 20:32:14.476 TimingTest[11159:3803] 72940.393701
2014-08-25 20:32:15.435 TimingTest[11159:540b] 72941.353001 Click
2014-08-25 20:32:15.477 TimingTest[11159:3803] 72941.395269
2014-08-25 20:32:16.435 TimingTest[11159:540b] 72942.353035 Click
2014-08-25 20:32:16.479 TimingTest[11159:3803] 72942.396839
2014-08-25 20:32:17.434 TimingTest[11159:540b] 72943.352364 Click
2014-08-25 20:32:17.479 TimingTest[11159:3803] 72943.397489
2014-08-25 20:32:18.435 TimingTest[11159:540b] 72944.353145 Click
2014-08-25 20:32:19.435 TimingTest[11159:540b] 72945.353202 Click
2014-08-25 20:32:20.435 TimingTest[11159:540b] 72946.353256 Click
2014-08-25 20:32:21.435 TimingTest[11159:540b] 72947.353306 Click
2014-08-25 20:32:22.435 TimingTest[11159:540b] 72948.353364 Click
2014-08-25 20:32:23.435 TimingTest[11159:540b] 72949.353420 Click
2014-08-25 20:32:24.435 TimingTest[11159:540b] 72950.353476 Click
2014-08-25 20:32:25.435 TimingTest[11159:540b] 72951.353530 Click
2014-08-25 20:32:26.435 TimingTest[11159:540b] 72952.353598 Click
2014-08-25 20:32:27.435 TimingTest[11159:540b] 72953.353637 Click
2014-08-25 20:32:28.435 TimingTest[11159:540b] 72954.353691 Click
2014-08-25 20:32:28.479 TimingTest[11159:3803] 72954.398038
2014-08-25 20:32:28.480 TimingTest[11159:3803] Slept too long: 11.000549
2014-08-25 20:32:29.435 TimingTest[11159:540b] 72955.353752 Click

2 个答案:

答案 0 :(得分:1)

这是App Nap的结果。见WWDC 2013 video Improving Power Efficiency with App Nap。调度计时器源示例大约是视频的30分钟。

您可以通过提供DISPATCH_TIMER_STRICT作为dispatch_source_create的第三个参数来告知您的计时器不参与,尽管除非绝对需要(例如,与不能容忍计时器偏差的硬件接口),否则显然不鼓励这样做,如你失去了App Nap提供的节能。

答案 1 :(得分:1)

正如罗布在上面指出的那样,最大的问题是App Nap(这是一个偷偷摸摸的小东西)。使用DISPATCH_TIMER_STRICT为我的调度计时器工作,但不适用于一般的睡眠/等待。

这是我如何修复可怕的计时器准确度:

1)根据需要使用适当的标志打开串口时启动活动。

self.serialActivity = [[NSProcessInfo processInfo] beginActivityWithOptions:
(NSActivityLatencyCritical | 
NSActivityIdleSystemSleepDisabled | 
NSActivityAutomaticTerminationDisabled | 
NSActivitySuddenTerminationDisabled | 
NSActivityBackground) 
reason:@"Serial Port IO"];

2)关闭串口时结束活动:

[[NSProcessInfo processInfo] endActivity:self.serialActivity];

现在我的计时器甚至可以使用usleep或sleepForTimeInterval。