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

代码如下:

- (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时,无论并发队列还是串行队列,代码都在不同的线程中执行。因此严格来说,我没有在添加了的同一个线程中使计时器无效,但在并发线程中却使了该计时器无效。

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

最佳答案

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

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

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

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

关于ios - 为什么我不能在dispatch_async串行队列中停止计时器?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/52315181/

10-16 04:13