Proper Way to Exit an NSThread

Moderator
Posts: 3,577
Joined: 2003.06
Post: #1
I have a project where I use two threads: 1) for OpenGL rendering and 2) for updating the simulation, physics, etc. Both have one timer each to call them back regularly. One of the things that came to my attention yesterday is that my dealloc method of my NSOpenGLView was not being called. After investigating further I had forgotten that not only do the timers retain the view but so does the extra thread for the sim (most example code I've seen forgets about the timer retain too, so I don't feel too bad). So, I have four retains on the GLView after everything is up and running:

- window (presumably)
- render timer
- update simulation timer
- update simulation thread

To get rid of the timer retains I simply call [timer invalidate] in the NSWindowWillCloseNotification -- simple enough. The window gets rid of its own retain. But the crapper is that the thread doesn't want to give up very easily. Calling [NSThread exit] is not only `not preferred' according to Apple docs, but it hangs in my situation for whatever reason. The way I have somewhat stumbled upon getting the thread to release itself is to set up a dummy port in the thread. I have scoured the Apple docs as much as my patience will allow, but they do not say specifically whether it's a bad idea or not. Indeed, in the RunLoop docs they mention that you should set up an empty port if you want to ensure that your runloop won't exit. I was under the impression that I needed to specifically add the port to the runloop, but if I do that then it crashes on app termination (works okay on window close though).

What I suspect is that when I call [NSPort port] it automatically hooks it up the the NSDefaultRunLoopMode, but it doesn't say that anywhere in the docs (that I've found). OR, it could be that when I invalidate the port, some message or notification automatically gets sent to the runloop and that triggers it to do its obligatory handling of one message and then returns from runMode:beforeDate:, which in turn exits the thread. Invalidating the timer is not enough to get runMode:beforeDate: to return -- apparently timers don't count the same as other inputs to the runloop.

This is kind of complicated to describe, maybe some code snippets would help better illustrate. This works as advertised and calls dealloc when I close the window or quit, as it should:
Code:
@interface GLView : NSOpenGLView
{
    NSTimer    *renderTimer;
    NSTimer    *updateTimer;
    NSPort    *dummyPortForExitingRunLoop;
}

@end

#define    SIM_TICK    110.0

@implementation GLView

- (id)initWithFrame:(NSRect)frameRect
{
    ...
    // standard OpenGL context init stuff here
    ...
    
    [NSThread detachNewThreadSelector:@selector(initSimThread:) toTarget:self withObject:nil];
    
    renderTimer = [NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(renderFrame) userInfo:nil repeats:YES];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shutdownRenderTimer:) name:NSWindowWillCloseNotification object:[self window]];
    
    return self;
}

- (void)initSimThread:(id)object
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    updateTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 / SIM_TICK target:self selector:@selector(updateSimulation) userInfo:nil repeats:YES];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(shutdownSimThread:) name:NSWindowWillCloseNotification object:[self window]];
    
    dummyPortForExitingRunLoop = [NSPort port];
    //[[NSRunLoop currentRunLoop] addPort:dummyPortForRunLoop forMode:NSDefaultRunLoopMode];  // <- can't do this or it crashes on quit
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    
    [pool release];
}

- (void)shutdownSimThread:(NSNotification *)notification
{
    [dummyPortForExitingRunLoop invalidate];
    [updateTimer invalidate];
}

- (void)shutdownRenderTimer:(NSNotification *)notification
{
    [renderTimer invalidate];
}

- (void)dealloc
{
    NSLog(@"dealloc is definitely being called");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

@end
So my big fat question is this: Is this okay to do, or is there a better way to do it? Specifically, I mean, is using dummyPortForExitingRunLoop, as I am doing, a proper way to exit my sim thread?
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #2
Man, threads can be tricky for the uninitiated! >me<

I started this (forum) thread in the first place because I felt that the way I was using a `dummy' NSPort to exit the thread was funky. It appears that it really is. I later discovered that if I typed any key, that port would become invalidated for some totally unknown reason to me. So I did some more fiddling around to come up with a better solution.

One of the directions I tried again was trying to figure out why [NSThread exit] was causing a hang. I figured out why. I was calling [NSThread exit] in my notification callback, assuming that it was going to be in the secondary thread from where I registered it. NOT! Notification callbacks are handled in the main thread. I got bit...

So for now I will just set a flag from the notification callback indicating that the thread should exit, and when the timer fires next time I invalidate the timer and call [NSThread exit]. It works great. While Apple says that is not the preferred method, it sure seems to work in a much more straightforward manner than all the other things I've tried.

Anyway, the call still stands: If anyone knows a better way than [NSThread exit] in this situation, I'm all ears (or eyes as it were).
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #3
You should not call [NSThread exit]; it will terminate the thread immediately, doing goodness only knows what to your application's memory (eg. you might leak memory, or leave an object in an invalid state).

The way I usually have threads exit is to check a global boolean periodically from the secondary thread -- when it becomes true, stop looping. There's even an example of this in the NSRunLoop documentation, so it's probably the right solution Wink
Quote this message in a reply
Moderator
Posts: 3,577
Joined: 2003.06
Post: #4
OneSadCookie Wrote:You should not call [NSThread exit]; it will terminate the thread immediately, doing goodness only knows what to your application's memory (eg. you might leak memory, or leave an object in an invalid state).

Right, that's kind of how I read Apple's docs, but I felt I was left with little choice. They didn't say that it couldn't be used, but rather should be avoided if at all possible.

Quote:The way I usually have threads exit is to check a global boolean periodically from the secondary thread -- when it becomes true, stop looping. There's even an example of this in the NSRunLoop documentation, so it's probably the right solution Wink

The particular example I was zooming in on earlier in the run loop documentation checks the flag once every second. Maybe that will work. It seemed like it wouldn't be a good fit for what I'm trying to do, but on third glance, maybe it is after all... I'll give it a try. I think I skipped over it initially because I read it like it was assuming there was a valid input for the NSRunLoop. But now that I think of it, I don't really care about that, just the global flag. That should work. Thanks. Smile
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Proper game update loop in Cocoa? LoneIgadzra 13 6,697 May 30, 2008 05:49 AM
Last Post: ThemsAllTook
  "Command /.../jam failed with exit code 1"- huh? Joseph Duchesne 2 5,839 Mar 29, 2006 09:32 AM
Last Post: socksy
  Discussion about only having one exit point in a function? Malarkey 21 7,292 Nov 24, 2005 09:48 PM
Last Post: PowerMacX