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

Why can not I stop a timer in dispatch_async serial queue?

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

代码如下:

- (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 秒得到一个输出 "Hey!",这里没问题。我知道在分派的线程中我必须 运行 运行 显式循环。

当我试图停止定时器时,问题就出来了。

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

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

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

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

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

我猜:

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

问题是您有一个串行队列,您在其上调用 [[NSRunLoop currentRunLoop] run]. But you’re not returning from that call (as long as there are timers and the like on that run loop). As the run documentation 说:

If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.

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

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