I'm trying to create a thread that configures a run loop to run a physics engine through a defined NSTimer. However, I'm having trouble making the thread exit normally (or I think the problem is).
Attached are the relevant portions of my code:
(This code is in a view controller)
(back is called when a button is pressed)
- (void)back {
[timestep invalidate];
exiting = YES;
[self release];
}
- (void)initializePhysicsWorld {
// Initializes the thread to simulate physics interactions.
[NSThread detachNewThreadSelector:#selector(physicsThreadMethod)
toTarget:self
withObject:nil];
}
- (void)physicsThreadMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
timestep = [NSTimer timerWithTimeInterval:1.0f/60.0f
target:self
selector:#selector(step:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:timestep forMode:NSDefaultRunLoopMode];
while (!exiting) {
CFRunLoopRun();
[pool release];
pool = [[NSAutoreleasePool alloc] init]; // periodically refreshes pool
}
CFRunLoopStop([myRunLoop getCFRunLoop]);
NSLog(#"Thread is going to exit");
[pool release];
}
- (void)dealloc {
if ([self.view superview]) {
[self.view removeFromSuperview];
}
[super dealloc];
}
The engine (the step: function) runs fine, but when I try to exit the loop by running the method back, it would appear that the thread does not release its retain on my view controller (dealloc is not called). I think my thread didn't exit the physicsThreadMethod method as the NSLog does not appear in the console. Dealloc was only called when I run 'back' a second time.
I'm not really sure why this is happening, so I would really appreciate any help. Thanks!
The problem lies in here:
while (!exiting) {
CFRunLoopRun(); //<-- here you start the run loop.
// the lines under this line are NEVER executed. also the while loop does nothing
[pool release];
pool = [[NSAutoreleasePool alloc] init]; // periodically refreshes pool
}
You could use NSOperations to wrap your work, there are already properties defined on this class to do exactly this kind of thing.
If you want to stick with your implementation in a NSThread you have to take a look at the CFRunLoop reference and how to add observers to the run loop.
Does CFRunLoopRun return every step:? CFRunLoopStop or [myRunLoop runUntilDate:] would help.
Related
For code like:
// Code in some object that will do work for an application:
- (BOOL)shouldBeRunning
{
[lockRunning lock];
BOOL shouldBeRunning= shouldRun;
[lockRunning unlock];
return shouldBeRunning;
}
- (void)stopRunning
{
[lockRunning lock];
shouldRun= FALSE;
[lockRunning unlock];
}
- (void)threadEntryPoint:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// From an example I saw awhile back:
// 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.
NSMachPort *dummyPort = [[NSMachPort alloc] init];
[runLoop addPort:dummyPort forMode:NSDefaultRunLoopMode];
[dummyPort release];
[pool release];
while ([self shouldBeRunning])
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[loopPool drain];
}
}
- (BOOL)startRunning:(NSError **)errorPtr
{
[self stopRunning]; // Stop if we are already running.
[runWorker release];
runWorker= [[NSThread alloc] initWithTarget:self selector:#selector(threadEntryPoint:) object:nil];
if(!runWorker)
return (FALSE);
// Start up the thread.
shouldRun= TRUE;
[runWorker start];
return TRUE;
}
- (void)doLotsOfStuff
{
// Some operation that is long and intensive
// that should be done in the background.
// This function will call the app delegate, which will display the
// results. It will also notify the app on completion.
}
- (void)doStuff
{
// Commented out for illustrative purposes.
//[self startRunning]; // Fire thread up.
[self performSelector:#selector(doLotsOfStuff) onThread:runWorker withObject:nil waitUntilDone:NO];
}
// Out in the delegate:
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
// Do setup....
[workObject startRunning]; // Start the worker thread in the worker object.
}
- (void)buttonHandler:(id)sender
{
[workObject doStuff];
}
So, in the application there is a button. The user will press it, and a task will run on a worker thread. The task will provide feedback to the application. In this case, the button is disabled until the task completes. I just do not want to show all that code.
With the code as written, if I press the button once, the task runs without delay. Often, a second button press yields the same result. However, sometimes the second press, but almost always the third or after, will result in a significant delay in performing the task. I put debug statements in and can observe that the code does the performSelector on the thread, then there is a delay, and finally the task runs.
If I uncomment the line in doStuff that re-creates the thread (making the one in applicationDidFinishLaunching redundant), of course it works perfectly every time.
From what I can tell, the thread is getting into an unresponsive state.
Any ideas on what might be going on? Anything obviously wrong with the setup and handling code? Any input appreciated.
In Apple's MVCNetworking sample code, the NetworkManager class includes this method to maintain a run loop in a secondary thread dedicated to network activity (in order to run NSURLConnection asynchronously):
- (void)networkRunLoopThreadEntry
{
while(YES) {
NSAutoreleasePool *pool;
pool = [[NSAutorelease alloc] init];
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
}
Since the run method exits immediately if there is no source attached to the run loop, this looks like an infinite while loop which is going uselessly to consume CPU resources if there is currently no NSURLConnection attached to the run loop.
On the other hand, to keep the run loop active, some suggests to schedule an empty port in the run loop:
- (void)networkRunLoopThreadEntry
{
NSAutoreleasePool *pool = [[NSAutorelease alloc] init];
NSPort *port = [NSPort port];
[[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
[NSRunLoop run];
[pool drain];
}
However, in this case, my worry is that the run method will never exit, which means the pool will never get drained, which means all objects allocated and autoreleased in the secondary thread will leak.
What is the way to go then?
(For the context, as many others, I'm trying to encapsulate an asynchronous NSURLConnection inside a NSOperation, which means it can be triggered outside of the main thread. Also, the MVCNetworking sample code, as well as the WWDC 2010 sessions Network Apps for iPhone OS, seem to suggest it is a good idea to have a unique secondary thread dedicated to network transfers to prevent latency on the main thread.)
You can create a CFRunLoopObserver for the kCFRunLoopBeforeWaiting activity and add it to the run loop. In the observer's callout, release the old pool and create a new one. Untested example:
static void resetPoolCallout(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
NSAutoreleasePool **poolPointer = (NSAutoreleasePool **)info;
[*poolPointer release];
*poolPointer = [[NSAutoreleasePool alloc] init];
}
- (void)networkRunLoopThreadEntry {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSPort *port = [NSPort port];
[[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
CFRunLoopObserverContext observerContext = {
.version = 0,
.info = (void*)&pool,
.retain = NULL,
.release = NULL,
.copyDescription = NULL
};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting,
true, 0, resetPoolCallout, &observerContext);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
[[NSRunLoop currentRunLoop] run];
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
[pool release];
}
I'm trying to learn about NSTimer, using Foundation and printing to the console. Can anybody tell me what I need to do to get the following to work? It compiles with no errors, but does not activate my startTimer method -- nothing prints.
My aim is to get one method to call another method to run some statements, and then stop after a set time.
#import <Foundation/Foundation.h>
#interface MyTime : NSObject {
NSTimer *timer;
}
- (void)startTimer;
#end
#implementation MyTime
- (void)dealloc {
[timer invalidate];
[super dealloc];
}
- (void)startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(runTimer:) userInfo:nil repeats:YES];
}
- (void)runTimer:(NSTimer *)aTimer {
NSLog(#"timer fired");
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MyTime *timerTest = [[MyTime alloc] init];
[timerTest startTimer];
[timerTest release];
[pool release];
return 0;
}
The timer never gets a chance to fire in your program, because the program ends almost immediately after the timer is created.
There's a construct called the Run Loop which is responsible for processing input, including input from timers. One run loop is created for each thread, but it isn't automatically started in this case.
You need to run the run loop and keep it going until the timer has a chance to fire. Fortunately, this is quite easy. Insert:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
between sending startTimer and release to timerTest. If you want the timer to repeat, you'll need to continue keeping the run loop active.
Note that you only need to do that in a simple program like this; when you are creating an application with a GUI, the run loop will be started via the Cocoa application setup process, and will remain active until the application terminates.
You have to add your timer to the default runloop after initializing it:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
Put this in -(void)startTimer.
I've been searching for and attempting to program for myself, an answer to this question.
I've got a secondary thread running inside my mainView controller which is then running a timer which counts down to 0.
Whilst this timer is running the secondary thread which initiated the timer should be paused/blocked whatever.
When the timer reaches 0 the secondary thread should continue.
I've Experimented with both NSCondition and NSConditionLock with no avail, so id ideally like solutions that solve my problem with code, or point me to a guide on how to solve this. Not ones that simply state "Use X".
- (void)bettingInit {
bettingThread = [[NSThread alloc] initWithTarget:self selector:#selector(betting) object:nil];
[bettingThread start];
}
- (void)betting {
NSLog(#"betting Started");
for (int x = 0; x < [dealerNormalise count]; x++){
NSNumber *currSeat = [dealerNormalise objectAtIndex:x];
int currSeatint = [currSeat intValue];
NSString *currPlayerAction = [self getSeatInfo:currSeatint objectName:#"PlayerAction"];
if (currPlayerAction != #"FOLD"){
if (currPlayerAction == #"NULL"){
[inactivitySeconds removeAllObjects];
NSNumber *inactivitySecondsNumber = [NSNumber numberWithInt:10];
runLoop = [NSRunLoop currentRunLoop];
betLooper = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(betLoop) userInfo:nil repeats:YES];
[runLoop addTimer:[betLooper retain] forMode:NSDefaultRunLoopMode];
[runLoop run];
// This Thread needs to pause here, and wait for some input from the other thread, then continue on through the for loop
NSLog(#"Test");
}
}
}
}
- (void)threadKiller {
[betLooper invalidate];
//The input telling the thread to continue can alternatively come from here
return;
}
- (void)betLoop {
NSLog(#"BetLoop Started");
NSNumber *currentSeconds = [inactivitySeconds objectAtIndex:0];
int currentSecondsint = [currentSeconds intValue];
int newSecondsint = currentSecondsint - 1;
NSNumber *newSeconds = [NSNumber numberWithInt:newSecondsint];
[inactivitySeconds replaceObjectAtIndex:0 withObject:newSeconds];
inacTimer.text = [NSString stringWithFormat:#"Time: %d",newSecondsint];
if (newSecondsint == 0){
[self performSelector:#selector(threadKiller) onThread:bettingThread withObject:nil waitUntilDone:NO];
// The input going to the thread to continue should ideally come from here, or within the threadKiller void above
}
}
You can't run a timer on a thread and sleep the thread at the same time. You may want to reconsider whether you need a thread at all.
There's a few things that need to be pointed out here. First, when you schedule your timer:
betLooper = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(betLoop:)
userInfo:nil
repeats:YES];
it's added to and retained by the current run loop by that method, so you don't need to do that manually. Just [myRunLoop run]. Your timer's selector argument is also invalid -- a timer's "target method" needs to look like this:
- (void)timerFireMethod:(NSTimer *)tim;
This also means that you don't need to retain the timer if all you want to do is invalidate it, since you will have a reference to it from inside that method.
Second, it's not clear what you mean by "this thread needs to sleep to wait for input". When you schedule that timer, the method (betLoop) is called on the same thread. If you were to sleep the thread, the timer would stop too.
You seem to be a little mixed up regarding methods/threads. The method betting is running on your thread. It is not itself a thread, and it's possible to call other methods from betting that will also be on that thread. If you want a method to wait until another method has completed, you simply call the second method inside the first:
- (void)doSomethingThenWaitForAnotherMethodBeforeDoingOtherStuff {
// Do stuff...
[self methodWhichINeedToWaitFor];
// Continue...
}
I think you just want to let betting return; the run loop will keep the thread running, and as I said, the other methods you call from methods on the thread are also on the thread. Then, when you've done the countdown, call another method to do whatever work needs to be done (you can also invalidate the timer inside betLoop:), and finalize the thread:
- (void)takeCareOfBusiness {
// Do the things you were going to do in `betting`
// Make sure the run loop stops; invalidating the timer doesn't guarantee this
CFRunLoopStop(CFRunLoopGetCurrent());
return; // Thread ends now because it's not doing anything.
}
Finally, since the timer's method is on the same thread, you don't need to use performSelector:onThread:...; just call the method normally.
You should take a look at the Threading Programming Guide.
Also, don't forget to release the bettingThread object that you created.
NSThread has a class method + (void)sleepForTimeInterval:(NSTimeInterval)ti. Have a look at this :).
NSThread Class Reference
I have a piece of network code that uses AsyncSocket but moves it to a separate runloop. I'm creating this runloop with the following piece of code:
[NSThread detachNewThreadSelector:#selector(_workerLoop) toTarget:self withObject:nil];
and here's how my _workerLoop looks like (they're both in the same class):
-(void)_workerLoop {
workerLoop = [[NSRunLoop currentRunLoop] retain];
while(keepWorkerLoopRunning) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[workerLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.5f]];
[pool release];
}
[workerLoop release];
workerLoop = nil;
}
Now, according to the docs, the NSThread will retain the target, and as this thread will only terminate when AsyncSocket disconnects, it's impossible to release and deallocate this object until the socket disconnects.
How can I fix this or maybe I'm doing something wrong?
I've solved this by refactoring the runloop constructor out into own class, referenced by parent class that handles the networking code. This way, parent object is being deallocated, it can stop the thread and release the runloop