3

It's simply an experimental code, but I got confused since the code didn't execute as I supposed.

The code is like:

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

Now, I got a output "Hey!" every 1 second, no problem here. I do know that in a dispatched thread I have to run the runloop explicitly.

The problem came out when I tried to stop the timer.

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

Actually the code in block wouldn't even execute!

What's more, if I used concurrent queue here (dispatch_asyn(dispatch_get_global_queue(...), ^{...})) it would be all right.

Things I know: each time I dispatch_async, no matter concurrent or serial queue, the code execute in different thread. So strictly I didn't invalidate the timer in the same thread where I added it, but it did invalidate in concurrent thread.

So my question is why it failed to invalidate in serial queue?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
刘maxwell
  • 187
  • 10
  • Possible duplicate of [Run repeating NSTimer with GCD?](https://stackoverflow.com/questions/10522928/run-repeating-nstimer-with-gcd) – DonMag Sep 13 '18 at 14:30

2 Answers2

2

The issue is that you have a serial queue on which you call [[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 says:

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.

That has the effect of blocking your serial queue’s thread. Any code dispatched to that queue (such as your attempt to invalidate the timer) won’t run as long as that thread is blocked. You have a “Catch 22".

On top of that, if you’re going to set up a background thread to run a NSTimer, you’ll want to create your own thread for that, not use one of the GCD worker threads. See https://stackoverflow.com/a/38031658/1271826 for an example. But as that answer goes on to describe, the preferred method for running timers on a background thread are dispatch timers, getting you out of the weeds of manipulating threads and run loops.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

I guess:

In a serial queue, a task is ready to execute only if its predecessor is finished. Here since a runloop which fires a timer is running, the task of invalidating the timer is waiting (blocked). So the code block is never executed.

刘maxwell
  • 187
  • 10