Problems launching an NSTimer from a secondary thread - iphone

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].

Related

Best way to implement by multithreading

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];
}
}

NSRunLoop is calling my selector after long Delay

Problem statement : we have one secondary thread in which we are doing all backend processing.In this secondary thread we have created a separate NSRunLoop to run. we create and use timer in this runloop
NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
NSRunLoop * threadRL = [NSRunLoop currentRunLoop];
[threadRL addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[threadRL run];
[myPool release];
Every thing is run fine except one of the call to a selector is taking almost 10 second to execute and this happens randomly not every time.
[myclass performSelector:#selector(func) onThread:myThread withObject:nil waitUntilDone:NO];
I tried this also with no difference.
[myclass performSelector:#selector(func) onThread:myThread withObject:nil waitUntilDone:NO modes:[NSArray arrayWithObjects: NSDefaultRunLoopMode, NSRunLoopCommonModes,nil]];
I am not doing any task in func which could possibly take this much time,
What I am thinking is that it might be possible that runloop is in different mode.
Is there a way to make fund be executed in highest priority i.e what ever is being executed in runloop be interrupted or something like that.
You could create a NSTimer and set the firedate to now. Please see the sample code below.
NSTimer *timer = [[NSTimer alloc] initWithFireDate:now
interval:.01
target:myClass
selector:#selector(funct)
userInfo:nil
repeats:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
[runLoop run];
I am answering my own question. My runloop was waiting on NSInputStream:read when there was no byte to read. So the func was being queued on in runloop source but as the thread is waiting in on socket read nothing was getting executed.
I got to know this when I hooked the runloop and saw it is getting stuck there.

NSTimer to update label

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateTimerDisplay) userInfo:nil repeats:YES];
[runLoop addTimer:self.timer forMode:NSRunLoopCommonModes];
This code-snippet is copied from my viewDidLoad method, so it is runned from the main-thread. All it do is to call a method to update a label.
I thought I need to have a own thread for doing this, but after getting help on this at SO I figured out that I did not.
However, I do not understand the NSRunLoopCommonModes. Why does it work?
AND the timer updates the label which is a "digital counter" which is on the same screen as a tableview so it CAN'T stop the timer even if the user holds the screen.
Thanks.
A NSRunLoop can run in different input modes. The mode defines which events are handled by the current runloop.
e.g.: If the current runloop is in event tracking mode, it only handles modal event loops. (e.g. dragging a NSScrollBar or a NSSlider on the Mac)
If you add your NSTimer only for NSDefaultRunLoopMode it won't fire if something is causing a modal event loop. (Details in Apple's documentation)
NSRunLoopCommonModes is an "alias" for multiple modes so that you don't have to do:
[[NSRunLoop currentRunLoop] addTimer:mRenderDurationTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:mRenderDurationTimer forMode:NSModalPanelRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:mRenderDurationTimer forMode:NSEventTrackingRunLoopMode];
I don't think you have to have this line at all, the first line is enough... I use PSYBlockTimer in my code which derives from the SDK method you use, but instead of a selector calls a block :
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:NO usingBlock:^ (NSTimer *t)
{
// stuff that will get executed in a second
}];
If you add your time to an instance of NSRunLoop under another thread, you need a while loop for this NSRunLoop of the thread. It looks like following:
do {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]];
} while (bDone);
Normally, I add the above code on my thread main function, and when thing is done, the thread go die and the autorelease pool of the thread will be released.

Converting non-main runloop tasks to GCD

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.

NStimer -- what am I doing wrong here?

I've been using an NSTimer successfully, but am now having trouble with it. Undoubtably something stupid. Appreciate another set of eyes. Running the debugger, I see that applicationDidFinishLaunching is called, but trigger is never called.
-(void) trigger:(NSTimer *) theTimer{
NSLog(#"timer fired");
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
nst = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(trigger) userInfo:nil repeats:YES];
[window makeKeyAndVisible];
}
The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
so you need
#selector(trigger:)
--edit--
Maybe you are doing this somewhere else, but in the code you included you do not actually start the timer. You have to add it to a NSRunLoop before it can trigger any events at all.
[[NSRunLoop currentRunLoop] addTimer:nst forMode:NSDefaultRunLoopMode];
If I read the examples correctly. I've only used the one the init method that automatically adds it to the current NSRunLoop. You really should look at the developer docs that someone included in the comments to my post.
Two things:
1) as others say, the method should have the following signature..
-(void) trigger:(NSTimer *) theTimer;
and you make the timer thus:
nst = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(trigger:) userInfo:nil repeats:YES];
2) merely creating the timer does not run it. As the documentation says:
You must add the new timer to a run
loop, using addTimer:forMode:. Then,
after seconds have elapsed, the timer
fires, invoking invocation. (If the
timer is configured to repeat, there
is no need to subsequently re-add the
timer to the run loop.)
Here's a piece of real functioning code that you can model after. The timer creation is the same as yours, but it also adds it to runloop the right way.
[[NSRunLoop currentRunLoop] addTimer:
[NSTimer timerWithTimeInterval:0.1
target:self
selector:#selector(someSelector:)
userInfo:nil
repeats:NO]
forMode:NSDefaultRunLoopMode];
The selector you're giving the timer, trigger, indicates that it should call a method that takes no parameter. Either change your timer-fired method to
- (void)trigger
{
// look at me, I don't take any parameters
NSLog(#"timer fired");
}
or change your initial timer call to use #selector(trigger:).
Your problem is due to the fact that timerWithTimeInterval:target:selector:userInfo:repeats: creates a timer but does not schedule it on the run loop, you have to do it yourself.
However, you may as well use this method which creates the timer and schedules it on the run loop: scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
I had a problem when starting timer in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { not in main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self startScheduledTimer];
});