1

In my main function for my command line program, I create a new instance of an NSThread subclass, and call start on it, where it runs a timer in a different thread. If the user wants to stop the timer, they type "stop" and I want it to end the thread as well.

How would I go about doing this? I'm gathering that I should call cancel on the thread, then in the main of the NSThread subclass check if isCancelled is YES, but as far as I know main is only called when I call start initially. I don't see where else I could check isCancelled in order to call [NSThread exit].

How should I handle exiting this NSThread?

Doug Smith
  • 29,668
  • 57
  • 204
  • 388

2 Answers2

3

You check for isCancelled in your NSThread subclass. You check for isCancelled throughout your code in NSThread subclass. When you call cancel, your NSThread subclass continues to run until it hits a check for isCancelled. What you do is place the isCancelled check multiple places in hopes when you call cancel it hits a isCancelled check and exits as soon as possible.

From your example code you posted I changed the TimerThread.m to look like this and it works fine:

#import "TimerThread.h"
#import "Giraffe.h"

@interface TimerThread () {
    Giraffe *giraffe;
}

@end

@implementation TimerThread

- (void)main {

    if (self.isCancelled)
        return;

    giraffe = [[Giraffe alloc] init];

    [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(calculate:) userInfo:nil repeats:YES];

    [[NSRunLoop currentRunLoop] run];
}

- (void)calculate:(NSTimer*)timer {
    if (self.isCancelled) {

        [timer invalidate];

        return;
    }

    [giraffe calculateValues:timer];
}

@end
random
  • 8,568
  • 12
  • 50
  • 85
  • But my NSThread subclass is only called once, and as far as I can tell only goes through execution once. – Doug Smith Jan 15 '14 at 16:15
  • The `NSThread` will exit once it's main function has been completed. Once it's completed you don't have to explicitly call `cancel` on it. – random Jan 15 '14 at 16:20
  • Can you post code? Are you meaning that you have other methods outside the `main` method that you want to check `isCancelled` in? – random Jan 15 '14 at 16:21
  • Here's a sample project with the situation I'm describing: http://cl.ly/2u28450S390l Basically, I call `start` on the NSThread subclass and it runs a timer. A little later (but before the main function returns) I want to stop the thread because the timer is no longer needed. The only time I interact with the subclass though is to start it, so I can't see where I'd put checks in that would be called later on. – Doug Smith Jan 15 '14 at 16:24
  • @DougSmith If you use a timer, you also need a run loop. That run loop will need to return as well, so that you can return from main. – CouchDeveloper Jan 15 '14 at 16:26
  • I have a run loop as well. Will that not end when I end the thread (as it's tied to the thread)? – Doug Smith Jan 15 '14 at 16:31
  • Thanks @random! How does `return` differ from `[NSThread exit]`? – Doug Smith Jan 15 '14 at 17:27
  • `return` is there to exit the method at that point. `NSThread exit` would kill the thread after the method had finished executing (I believe this is right someone please correct if I'm wrong). So if you just had `[NSThread exit]` `[giraffe calculateValues:timer]` would be called once more before the thread was exited. – random Jan 15 '14 at 18:06
  • Why not wrap the `calculateValues:` call in an `else` statement then? I need to kill the thread, so don't I need to call `[NSThread exit]` in order to do that? – Doug Smith Jan 15 '14 at 18:28
  • Calling `return` in the `calculate:` method I believe will kill the thread since it's `main` has finished executing. You can try putting `[NSThread exit]` before the `return` or remove the `return` wrap it in an `if else` like you said and try it. I went with the quickest solution that worked for me. – random Jan 15 '14 at 19:33
1

Based on the various comments, you likely want a main method as follows:

+ (void) threadMain
{
    @autoreleasepool {
        [[NSThread currentThread] setName:@"WorkerThread.sharedThread"];
        NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        BOOL done = NO;
        while (!done) {
            @autoreleasepool {
                [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
                // check termination status:
                done = ...;
            }
        }
    }
}

A few explanations:

  • [runLoop addPort:port] ensures that the run loop has at least one event source, otherwise, the run loop may return prematurely.

  • The run loop returns after every processed event source. That is, you may check the status after something happened (a timer for example, scheduled on this run loop whose mode equals NSDefaultRunLoopMode).

  • The inner autorelease pool is required, since [NSDate distantFuture] returns an autoreleased object - this may stack up unless you have that autorelease pool.

  • Variable done must be set from code which executes on this thread - otherwise, you need memory barriers or other synchronization primitives in order to ensure that the change made on a different thread is "visible" in this thread.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • +1 well said @CouchDeveloper This actually helped me tweak an existing project :P – random Jan 15 '14 at 17:06
  • @random Out of curiosity: what could you fix? – CouchDeveloper Jan 15 '14 at 17:16
  • I have been playing around with optimal ways to parse large amounts of data into CoreData and had some NSOperation subclasses sharing the workload. I was using a solution similar to what I provided him but what you posted lead me to a different solution that may be cleaner. :) – random Jan 15 '14 at 18:09