【问题标题】:Why can not I stop a timer in dispatch_async serial queue?为什么我不能在 dispatch_async 串行队列中停止计时器?
【发布时间】:2019-02-18 06:37:11
【问题描述】:

这只是一个实验代码,但我很困惑,因为代码没有按我的预期执行。

代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.myQueue = dispatch_queue_create("com.maxwell.timer", NULL);
    dispatch_async(self.myQueue, ^{
        self.timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"Hey!");
        }];
        [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    });
}

现在,我得到了一个输出“嘿!”每1秒,这里没问题。我确实知道在分派的线程中我必须明确地运行 runloop。

当我试图停止计时器时,问题就出现了。

- (void)stopTimer { 
    dispatch_async(self.myQueue, ^{
        [self.timer invalidate];
        self.Timer = nil;
    });
}

实际上block中的代码甚至都不会执行!

而且,如果我在这里使用并发队列(dispatch_asyn(dispatch_get_global_queue(...), ^{...}))就可以了。

我知道的事情:每次我dispatch_async,无论是并发队列还是串行队列,代码都在不同的线程中执行。所以严格来说我没有在我添加它的同一线程中使计时器无效,但它确实在并发线程中无效。

所以我的问题是为什么它无法在串行队列中失效?

【问题讨论】:

标签: ios grand-central-dispatch nstimer


【解决方案1】:

问题是您有一个串行队列,您可以在其上调用[[NSRunLoop currentRunLoop] run]。但是你不会从那个调用中返回(只要那个运行循环上有计时器等)。正如run documentation 所说:

如果没有输入源或计时器附加到运行循环,则此方法立即退出;否则,它通过重复调用runMode:beforeDate:NSDefaultRunLoopMode 中运行接收器。换句话说,此方法有效地启动了一个无限循环,该循环处理来自运行循环的输入源和计时器的数据。

这会阻塞串行队列的线程。只要该线程被阻塞,任何分派到该队列的代码(例如您尝试使计时器无效)都不会运行。你有一个“Catch 22”。

最重要的是,如果您要设置一个后台线程来运行 NSTimer,您需要为此创建自己的线程,而不是使用 GCD 工作线程之一。有关示例,请参阅https://stackoverflow.com/a/38031658/1271826。但正如该答案继续描述的那样,在后台线程上运行计时器的首选方法是调度计时器,让您摆脱操纵线程和运行循环的杂草。

【讨论】:

    【解决方案2】:

    我猜:

    在串行队列中,任务只有在其前身完成时才准备好执行。在这里,由于触发计时器的 runloop 正在运行,因此使计时器无效的任务正在等待(阻塞)。所以代码块永远不会被执行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多