2

I am trying to get a NSTimer to fire in a subthread. My code essentially looks like this:

-(void) handleTimer:(NSTimer *)theTimer {  
    NSLog(@"timer fired");  
}

-(void) startMyThread {  
    // If I put timer in here, right before I start my new thread, it fires.  
    // [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:NO];  
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];  
}

// This method is called from inside the new thread  
-(void) playNote:(Note *)theNote withTemplate:(NSString *)theTemplate X:(int)x andY:(int)y {
    NSLog(@"before timer");  
    // The timer doesn't fire in here. (But the print statements do.)  
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:NO];
    NSLog(@"after timer");  
}

I would really like to (read:need to?) fire the timer in the subthread because it is going to be used to stop notes from playing (and all of the note playing needs to happen in a subthread).

I must be missing something with how a NSTimer runs in subthreads...
Premature thanks for the help!

ThomasCle
  • 6,792
  • 7
  • 41
  • 81
rizzes
  • 1,532
  • 20
  • 33
  • don't think about what thread the handler will be called on. just concentrate on switching control to the background thread from the handler proper, and then do your processing there. – john.k.doe Jul 20 '12 at 06:22

4 Answers4

5

A timer won't fire unless it is scheduled in a run loop. You'll also want to avoid spinning up threads willy-nilly.

Unless your run method fires up a run loop, your timer won't fire.

A better solution, btw, might be to use GCD's dispatch_after(). It is lighter weight than a thread, but it depends on what you need to do. In general, though, queues are a more efficient means of adding concurrency to an app.


Grabbing the code from the comment for proper formatting (good find):

double delayInSeconds = .2;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    NSLog(@"dispatch popped");
});

One additional note; you might consider using one of the global concurrent queues. They won't block when the Main Event Loop might block. Of course, if you are updating the UI, you have to go back to the MEL anyway.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • 1
    Thanks for the pointer to dispatch_after(). I used this code, found from this SO post: [stackoverflow.com/questions/4139219/… `double delayInSeconds = .2; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"dispatch popped"); });` – rizzes Jul 20 '12 at 16:31
3

You should add the NSTimer to currentRunLoop. Your playNote method should look like this:

-(void) playNote:(Note *)theNote withTemplate:(NSString *)theTemplate X:(int)x andY:(int)y
{
    NSLog(@"before timer");  
    // The timer doesn't fire in here. (But the print statements do.)  
    NSTimer myTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(handleTimer:) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:myTimer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"after timer");  
}

That should do it :)

ThomasCle
  • 6,792
  • 7
  • 41
  • 81
1

Add the timer to the current runloop, and run.

-(void) handleTimer:(NSTimer *)theTimer {  
    NSLog(@"timer fired");  
}

-(void) startMyThread {  
    // If I put timer in here, right before I start my new thread, it fires.  
    // [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:NO];  
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];  
}

// This method is called from inside the new thread  
-(void) playNote:(Note *)theNote withTemplate:(NSString *)theTemplate X:(int)x andY:(int)y {
    NSLog(@"before timer");  
    // The timer doesn't fire in here. (But the print statements do.)  
    NSTimer *Timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:) userInfo:nil repeats:NO];
    NSLog(@"after timer");  
    [[NSRunLoop currentRunLoop] addTimer:Timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}
Parag Bafna
  • 22,812
  • 8
  • 71
  • 144
  • 3
    Because that is what `run` does; it doesn't return, it spins the run loop until the loop is terminated. Doesn't make your answer wrong, just a critical detail to consider. – bbum Jul 20 '12 at 16:13
0

Try to addTimer: within the [NSRunLoop mainRunLoop], not the current one !

tontonCD
  • 320
  • 2
  • 6