这只是一个实验代码,但由于代码没有按我预期的那样执行,我感到困惑。
代码类似于:
- (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
时,无论并发队列还是串行队列,代码都在不同的线程中执行。因此严格来说我没有在添加线程的同一个线程中使计时器无效,但在并发线程中却使该计时器无效。
所以我的问题是为什么它未能在串行队列中失效?
答案 0 :(得分:1)
问题是您有一个串行队列,在该队列上调用了[[NSRunLoop currentRunLoop] run]
。但是,您不会从该调用中返回(只要该运行循环中有计时器等)。正如run
documentation所说:
如果没有输入源或计时器附加到运行循环,则此方法立即退出;否则,它将通过反复调用
NSDefaultRunLoopMode
在runMode:beforeDate:
中运行接收器。换句话说,这种方法有效地开始了一个无限循环,该循环处理来自运行循环的输入源和计时器的数据。
这会阻止您的串行队列线程。只要该线程被阻止,调度到该队列的任何代码(例如您试图使计时器无效)都不会运行。您有一个“赶上22”。
最重要的是,如果您要设置一个后台线程来运行NSTimer
,则需要为此创建自己的线程,而不要使用GCD工作线程之一。有关示例,请参见https://stackoverflow.com/a/38031658/1271826。但是正如该答案所继续描述的那样,在后台线程上运行计时器的首选方法是调度计时器,这使您摆脱了操纵线程和运行循环的麻烦。
答案 1 :(得分:0)
我猜:
在串行队列中,仅当任务的前任完成时,任务才准备执行。这里,由于触发计时器的运行循环正在运行,因此使计时器无效的任务正在等待(阻止)。因此,代码块永远不会执行。