为什么我不能在dispatch_async串行队列中停止计时器?

时间:2018-09-13 13:48:27

标签: ios grand-central-dispatch nstimer

这只是一个实验代码,但由于代码没有按我预期的那样执行,我感到困惑。

代码类似于:

- (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;
    });
}

实际上,代码块中的代码甚至无法执行!

此外,如果我在此处使用并发队列(dispatch_asyn(dispatch_get_global_queue(...), ^{...}))没事。

我知道的事情:每当我dispatch_async时,无论并发队列还是串行队列,代码都在不同的线程中执行。因此严格来说我没有在添加线程的同一个线程中使计时器无效,但在并发线程中却使该计时器无效。

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

2 个答案:

答案 0 :(得分:1)

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

  

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

这会阻止您的串行队列线程。只要该线程被阻止,调度到该队列的任何代码(例如您试图使计时器无效)都不会运行。您有一个“赶上22”。

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

答案 1 :(得分:0)

我猜:

在串行队列中,仅当任务的前任完成时,任务才准备执行。这里,由于触发计时器的运行循环正在运行,因此使计时器无效的任务正在等待(阻止)。因此,代码块永远不会执行。