【问题标题】:Weird behaviour of dispatch_after()dispatch_after() 的奇怪行为
【发布时间】:2014-03-04 06:29:06
【问题描述】:

我正在编写一个可以同时执行多项任务的应用程序。一项特定任务是每 200 毫秒一次完成一项工作。为此,我使用了两种相互调用的方法。第一种方法只调用第二种方法,第二种方法使用 dispatch_after() 延迟调用第一种方法。

经过几次迭代(300-400 次),dispatch_after 中的块在 200 毫秒后不执行。 执行块需要大约 5-10 秒。请让我知道行为的原因(延迟)。我也尝试了 NSThread (sleepForTimeInterval:) 我也面临同样的问题。我被卡住了。请帮帮我。

代码如下。

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

【问题讨论】:

  • 通常发生这种情况的原因是,无论你做什么都需要很长时间,所以下一次运行循环旋转的时间已经过去了。
  • @GradyPlayer,为了检查行为,我简化了代码并发布在上面。在上面的代码中,我只是增加一个变量并打印它。我认为这不应该花费太多时间。如果我的理解是错误的,请您对此进行更多说明。
  • @SenthilGanesh,简化代码是否具有相同的行为?因为看起来你第二次分别调用了 fistMethod 或 secondMethod 而不是在你的 200ms 循环中。
  • @Cy-4AH,我在上面的代码中遇到了问题。对不起,我不明白你的意思。能详细点吗?
  • 该问题与“运行循环”无关。其他事情正在发生。

标签: objective-c multithreading macos objective-c-blocks nsthread


【解决方案1】:

发生这种情况时,您的应用是否在前台?如果没有,您可能只是看到 App Nap 开始发挥作用。它所做的其中一件事是在后台应用程序中限制计时器。

【讨论】:

  • App Nap 是否会将计时器最多延迟 5-10 秒(如问题中所述)?
  • @MartinR 不幸的是,文档中没有透露详细信息。可能,是的。也许,不是。需要调查这个问题。
  • @MartinR 我注意到当应用程序在后台运行时,我正在使用的调度源计时器(即DISPATCH_SOURCE_TYPE_TIMER)在后台运行时遇到了长达 10 秒的延迟,并且当我以编程方式禁用应用程序小睡(使用beginActivityWithOptions)时,那些 10 秒的延迟似乎消失了。
  • 确实可以10秒!感谢您的信息!
  • App Nap 肯定会延迟长达 10 秒。如果您在后台为用户做事,您需要使用 NSProcessInfo API 将它们标记为这样。如果它的工作对用户来说并不直接重要,那么允许它被限制是好的。
【解决方案2】:

一种可能的效果是“计时器合并”和“应用程序小睡”。

相关:本题:Have you noticed that dispatch_after runs ~10% too slow on iOS devices?.

如果这确实是您的问题的原因,您可以使用计时器(NSTimer)修复它,或者使用我们自己的实现,基于调度库,您可以在其中控制确切的行为。另见:Gist 上计时器的实现:RXTimer

编辑:

在 Mac OS X 上 App Nap 启动时,我们似乎无法分别控制巨大的“余地”延迟。

【讨论】:

  • 我不确定使用NSTimer(或 GCD 调度计时器源)是否能解决此问题,因为我对两者的测试表明延迟仍然可能发生。我通过致电[[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityUserInitiated reason:@"..."]; 进行补救
  • 感谢@CouchDeveloper、Rob 和 Martin R 的帮助!我使用 if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:reason:)]) { self.activity = [[NSProcessInfo processInfo] beginActivityWithOptions:0x00FFFFFF reason:@"For disabling App Nap"] 禁用了 App Nap }
  • @Rob @SenthilGanesh 我已经确认dispatch_source_set_timer() 在可以明确指定的余地值方面是准确的(在 iOS 7.0.x 上的设备上)。 RXTimer 的链接源使用dispatch_source_set_timer。 (当延迟设置为 1.0 秒时,测量的实际平均时间为 1001.2 毫秒。dispatch_after 在内部使用 10% 延迟的默认余量,例如达到 1100 毫秒)
  • @CouchDeveloper 嗯。我无法将其与我对调度计时器源的测试相协调,尽管设置了适当的余量值,但如果 App Nap 启动,它会间歇性地影响 OP 引用的间隔长达 10 秒。一旦 App Nap 被禁用,它就会按预期运行。
  • @Rob 是的,我在 Mac OS X 上错了,所以我很抱歉。任何可能记录在案的提示?
【解决方案3】:

我在 OS X 上遇到了同样的问题。我的延迟超过了指定延迟的 1000%。

在系统范围内关闭 App Nap 解决了余地:

defaults write NSGlobalDomain NSAppSleepDisabled -bool YES

【讨论】:

  • 您可以在每个应用程序中关闭它,但不能在系统范围内关闭。
  • @Daniel 为什么?测试该问题是否与 App Nap 相关似乎是公平的。
  • 确实如此。误解了你的意图。将尽快投票。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-08
  • 2015-07-20
  • 2010-10-03
  • 2021-07-12
  • 2013-10-04
  • 2019-01-23
相关资源
最近更新 更多