Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Objective-C run loop to stop and re-start method?

I used to think I was a reasonably intelligent person.
apple's "threading programming guide", has shattered my ego sustaining self deception.

I have a method I want to run repeatedly on a secondary thread, in the example below I've called this doStuff:
I want to be able to repeatedly stop and start the repeated calling of this method.

the code starts the thread.
if the boolean value stuffToDo is true,
then it calls doStuff:
otherwise
it has a little rest.
then it loops again until i tell it to stop

My current code seems wasteful, because it keeps checking 'stuffToDo' even when there is nothing to do.

I could get rid of stuffToDo and just spawn and cancel the thread as needed.
This also seems wasteful, and means I would need to be careful not to accidentally spawn a new thread when I already have one running.

I am sure that the answer to efficiently solving my predicament can be found somewhere in the "run loop management" section of Apple's "threading programming guide"
perhaps it involves custom input sources

But I am really finding this document challenging.
It is as if this document is spawning too many threads in my brain and computation grinds to a halt.

enum eRenderThreadMode
{
    render_not_started,
    render_run,
    render_cancel,
    render_finished
};


- (IBAction) startThread:(id)sender
{
    self.renderThreadMode = render_run;
    label.text = @"doing stuff"; 
    [NSThread detachNewThreadSelector:@selector(keepDoingStuff)  toTarget:self withObject:nil];

}

- (void)keepDoingStuff
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    while (renderThreadMode == render_run) 
    {
        if(stuffToDo)
        {
            [self doStuff];
        }
        else
        {
            [NSThread sleepForTimeInterval:kLittleRest];
        }
    }
    self.renderThreadMode = render_finished;
    [pool release];
}


- (IBAction)stopThread:(id)sender
{
    self.renderThreadMode = render_stop;

    while (self.renderThreadMode == render_cancel) 
    {
        [NSThread sleepForTimeInterval:kLittleRest];
    }
}
like image 565
compound eye Avatar asked Jul 18 '09 03:07

compound eye


2 Answers

You could use a synchronization object that your secondary thread sleeps on. This Apple page indicates there's a facility called Conditions that might do what you want. Using a Condition or similar synchronization object will let your thread only be woken up when there's work to be done (or when it's time for the thread to die).

like image 81
aem Avatar answered Nov 13 '22 06:11

aem


Yeah, you are correct that you want to use a runloop, what you are missing is how to set this all up. I am going to modify your post and explain what is going on. Don't worry if it intimidates you, it is tricky and there are some gotchas you only learn about from experience

- (IBAction) startThread:(id)sender
{
    self.renderThreadMode = render_run;
    label.text = @"doing stuff"; 
    self.backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(keepDoingStuff) object:nil];
    [self.backgroundThread start];    
}

//Okay, this is where we start changing stuff
- (void)keepDoingStuff
{
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        //A runloop with no sources returns immediately from runMode:beforeDate:
        //That will wake up the loop and chew CPU. Add a dummy source to prevent
        //it.

        NSRunLoop *runLopp = [NSRunLoop currentRunLoop];

        NSMachPort *dummyPort = [[NSMachPort alloc] init];
        [runLoop addPort:dummyPort forMode:NSDefaultRunLoopMode];
        [dummyPort release];
        [pool release];

        while (1) 
        {
                NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
                [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
                [loopPool drain];
        }
}

Okay, so at this point you should be looking at the above code and thinking "Well, that may be a nice sleeping thread, but it doesn't do anything. And that is true, but since it has an active runloop we can do anything that is runloop based against it, include performSelector:onThread:withObject:waitUntilDone:

- (void) doStuffOnBackgroundThread
{
    [self performSelector:@selector(doStff) onThread:self.backgroundThread withObject:nil waitUntilDone:NO];
}

When you call the above method on your main thread (or any other thread) it will marshall the various arguments and enqueue the runloop of the specified thread, waking it up as necessary. In this case that will cause self.backgroundThread to wakeup from runMode:beforeDate:, execute -doStuff, then cycle back aroound the loop and go back to sleep waiting in runMode:beforeDate:. If you want to be able to tear down the thread you can setup a variable in the while loop like you had in your code, though remember the thread would go away if it is sleep unless you wake it up, so it probably best to encapsulate that in a method that sets the control variable via a performSelector:onThread:withObject:waitUntilDone:, as a plus that will mean the variable will only ever be set from the background thread which simplifies synchronization issues.

Okay, so I think that solves your problem, so time to make the obligatory plug: Are you sure you want to be doing this with threads? NSOperation and NSOperationQueue may be a much simpler solution that takes care of all the threading issues for you if all you need to do is occasionally enqueue some data to have processed. They will schedule work, manage dependencies, and build up/tear down threads as well as taking care of all the runloop wake/sleep stuff.

like image 25
Louis Gerbarg Avatar answered Nov 13 '22 05:11

Louis Gerbarg