I'm kind of new to multithreading, and need some advice.
I'm using ARC in my code.
Problem : I've set up NSTimer in my app to fire every 1 second some method which creates and starts thread like this
//Create a new thread
mSomeThread = [[NSThread alloc] initWithTarget:self selector:#selector(someMethod) object:nil];
//start the thread
[mSomeThread start];
Where mSomeThread is an ivar
Let say the execution of mSomeThread takes more than 1 second, and the mSomeThread is allocated second time, i.e. according to ARC "rules" its released before be allocated one more time.
Does it mean that the first thread doesn't complete and and is forced to quite ?
An NSThread retains itself for the duration of its execution. There's no risk that resetting mSomeThread will cause a running thread to be terminated prematurely.
Yes. If you really need to keep reference to the current thread of execution for your someMethod then you need to wait for it to complete before you can actually start a new thread.
A quick way of doing this would be to add
while ([mSomeThread isExecuting]) {
sleep(1);
}
immediately after [mSomeThread start];.
By the way I'd rather re-implement NSThread and setup a repetitive NSTimer inside its main implementation.
Something like:
- main {
#autoreleasepool {
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(someMethod) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] run];
}
}
Related
I have a task that runs periodically and it was originally designed to run on a separate run loop than the main runloop using NSThread and NSTimer.
What's the best way to adapt this to take advantage of GCD?
Current code:
-(void)initiateSomeTask
{
[NSThread detachNewThreadSelector:#selector(startTimerTask)
toTarget:self withObject:nil];
}
-(void)startTimerTask
{
// We won't get back the main runloop since we're on a new thread
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
NSPort *myPort = [NSMachPort port];
[myRunLoop addPort:myPort forMode:NSDefaultRunLoopMode];
NSTimer *myTimer = [NSTimer timerWithTimeInterval:10 /* seconds */
target:self selector:#selector(doMyTaskMethod)
userInfo:nil repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSRunLoopCommonModes];
[myRunLoop run];
}
Is there anything I can do besides replace detachNewThreadSelector with dispatch_async?
You can replace the use of NSTimer with use of dispatch_source_create with DISPATCH_SOURCE_TYPE_TIMER. You won't need a run loop then.
Back in the original case, though, you don't really need to make a thread or use dispatch to run a timer. Kind of the point of run loops is that you don't need to make a thread to do something simple like a timer.
I'm trying to create a UILabel which will inform the user of what is going on while he waits. However the UILabel always delay its text update until after the system goes idle again.
The process:
[infoLine performSelectorOnMainThread:#selector(setText:) withObject:#"Calculating..." waitUntilDone:YES];
[distanceManager calc]; // Parses a XML and does some calculations
[infoLine performSelectorOnMainThread:#selector(setText:) withObject:#"Idle" waitUntilDone:YES];
Should not waitUntilDone make this happen "immediately"?
If you are doing this on the main UI thread, don't use waitUntilDone. Do a setText, setNeedsDisplay on the full view, set a NSTimer to launch what you want to do next starting 1 millisecond later, then return from your function/method. You may have to split your calculation up into chucks that can be called separately by the timer, maybe a state machine with a switch statement (select chunk, execute chunk, increment chunk index, exit) that gets called by the timer until it's done. The UI will jump in between your calculation chunks and update things. So make sure your chunks are fairly short (I use 15 to 200 milliseconds).
Yes waitUntilDone makes the setText: happen immediately, but setting the label's text does not mean the screen is updated immediately.
You may need to call -setNeedsDisplay or even let the main run loop tick once before the screen can be updated.
Here's a useful function I added to a subclass of UIViewController. It performs the selector in the next run loop. It works, but do you think I should make NSTimer *timer an instance variable since it's likely this method will be called more than once.
- (void)scheduleInNextRunloopSelector:(SEL)selector {
NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0.001]; // 1 ms
NSTimer *timer = [[NSTimer alloc]
initWithFireDate:fireDate interval:0.0 target:self
selector:selector userInfo:nil repeats:NO];
[fireDate release];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
}
Use performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval):
self.infoLine.text = #"Calculating...";
[self performSelector:#selector(likeABoss) withObject:nil afterDelay:0.001];
//...
-(void) likeABoss {
// hard work here
self.infoLine.text = #"Idle";
}
I aims to release a movieplayer (theMovie) and then start another action (so-called playButtonClicked) after it is completely released. I used performSelector to delay the "playButtonClicked" for 1 second and it works well. The code is:
[theMovie release];
[self performSelector:#selector(playButtonClicked) withObject:nil afterDelay:1];
However, I don't want to always delay 1 second. I want to start the "playButtonClicked" as soon as "theMovie" is completely released. I tried the following code, but it didn't work because [timer userInfo] never is nil.
Does anybody know how to check a movieplayer release finished.
[theMovie release];
//[self performSelector:#selector(playButtonClicked) withObject:nil afterDelay:1];
NSTimer *atimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:#selector(waitForReleaseFinish:)
userInfo: (MPMoviePlayerController*) theMovie repeats:YES];
The code of waitForRleaseFinish: (NSTimer *)timer is:
if ([timer userInfo]==nil) //here I actually want to test if (theMovie==nil),but I don't know how to do and I'm not sure if it is a correct way to determine the release finished.
{
[timer invalidate];
[self playButtonClicked];
}
Look forward to helps. Thank you.
There is no need.
If you just release the player and then call playButtonClicked, like this:
[theMovie release];
[self playButtonClicked];
It won't execute the second line until the first is completed, or until theMovie is released. This is all on the same thread so it will execute in order. You don't need a timer for this. Although in situations where what you're waiting for to finish executes on a new thread, you would use a callback, rather than guessing how long it takes (which is much less than 1 second!).
Also, just so you don't misunderstand, "completely releasing" is just subtracting the retainCount by one. It will automatically deallocate when it reaches zero.
Just as a side note, why is it important that theMovie is released (deallocated?) before playButtonClicked is executed?
Also, your waitForReleaseFinish: code would work, but it's unnecessary because theMovie would be released before the timer is created.
I have a NSThread that i would like to timeout after a certain amount of time.
[NSThread detachNewThreadSelector:#selector(someFuntion) toTarget:self withObject:nil];
- (void) someFunction {
//Some calculation that might take a long time.
//if it takes more then 10 seconds i want it to end and display a error message
}
Any help you can provide on this would be greatly appreciated.
Thanks,
Zen_silence
Instead of using NSThread, use NSOperation. You could then keep a reference to the operation, and set a NSTimer for 10 seconds. If the timer fires, tell the operation to cancel itself.
Try something like this:
workerThread = [[NSThread alloc] initWithTarget:self selector:#selector(someFunction) object:nil];
[workerThread start];
timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:workerThread selector:#selector(cancel) userInfo:nil repeats:NO];
Be sure that you (1) check workerThread.isCancelled when the thread finishes to see whether the thread timed out, and (2) call [timoutTimer invalidate] to clean up the timer if the thread did not time out.
I'm noticing in my code that when I try to start an NSTimer from a secondary thread, it doesn't work. I tried calling +[NSRunLoop currentRunLoop] just in case the problem was that the thread didn't have a run loop...but no dice. (Note that that was a shot in the dark. The docs said that would create a run loop, but perhaps there's other configuration that I needed to do, and didn't.)
I'm aware of calls like -[NSObject performSelectorOnMainThread:] which could solve my problem (in fact, my solution was to simply move this code into the primary thread, which works fine), but I'm still curious about why this problem occurred. Is it in fact impossible to start an NSTimer from a secondary thread? Is there a workaround?
Thanks very much.
The following code segment works for me.
-(id)init {
myWorkerThread = [[NSThread alloc]initWithTarget:self selector:#selector(workerThread) object:nil];
[myWorkerThread start];
}
#pragma mark WorkerThread Support
-(void)stillWorking {
NSLog(#"Still working...");
}
-(void)workerThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSTimer *threadTimer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(stillWorking)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:threadTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
[pool drain];
}
I found this page with some source code for starting an NSTimer on a secondary thread. Do you actually start the runloop in your code? It's tough to say without seeing your code what the problem may be:
http://www.iphonedevsdk.com/forum/iphone-sdk-development/22175-nstimer-secondary-thread-will-produce-leaks.html
John Franklin answer is correct..but when you call the method
scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:
it automatically schedules the timer on the current NSRunLoop. Therefore you don't need to add again the timer to the current run loop, you can only call a [[NSRunLoop currentRunLoop] run] method.
Make sure you A) add the timer to the current runloop, and B) run said runloop. When your timer fires, if you'd like to exit the [runloop run] invocation, call [runloop stop].