I have a timer who run in a runloop, the problem is when the timer end's and the user scroll the page at the same time, the apps crash with the problem:
bool _WebTryThreadLock(bool), 0x18d9be60: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
this is the timer:
- (void)startTimer{
NSThread* timerThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimerThread) object:nil]; //Create a new thread
[timerThread start]; //start the thread
}
-(void) startTimerThread
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[repeatingTimerEtape invalidate];
NSString * temps;
if (questionCircuit) {
temps = [[Mission singletonMission].circuitTerrain valeurChampEtapeCircuitEnCours:#"et_temps_etape" :FALSE];
}
else {
temps = [[Mission singletonMission].histoireTerrain valeurChampQuestion:#"hi_temps_etape" :FALSE];
}
if (!b_Pause) {
[dateComp setHour:0];
[dateComp setMinute:[[FonctionUtile gauche:temps :2] intValue]];
[dateComp setSecond:[[FonctionUtile droite:temps :2] intValue]];
}
else {
b_Pause = FALSE;
}
self.repeatingTimerEtape = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(updateLabelTimerEtape:) userInfo:nil repeats:YES];
[runLoop run];
[pool release];
}
thx
Tried to obtain the web lock from a thread other than the main thread or the web thread.
You started a thread then did stuff on it that should only be done on the main thread or the "web thread".
Most likely in your updateLabelTimerEtape: method. Look at the backtrace to know for sure.
Also, valeurChampQuestion::: is a horrible method name. Don't make methods that have arguments that lack a description. valeurChampQuestion:isSelected: would be better (replace isSelected: with a better description -- since others can't tell what it should be from that line of code, that is confirmation that bare arguments are bad).
Related
I am creating a number (~5) NSTimer instances from data stored in a SQLite database using the following code below:
-(void)setupNotificationTimer:(NSDictionary *)timerDetails{
NSLog(#"TIMER REPEATS VALUE: %#", [timerDetails objectForKey:#"repeats"]);
NSLog(#"INTERVAL: %f", [[timerDetails objectForKey:#"interval"] floatValue]);
bool repeat = [[timerDetails objectForKey:#"repeats"] boolValue];
if (repeat) {
NSLog(#"Should Repeat");
}
else{
NSLog(#"Should Not Repeat");
}
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:[[timerDetails objectForKey:#"interval"] floatValue]
target:self
selector:#selector(fireTimer:)
userInfo:timerDetails
repeats:repeat];
[timer fire];
[timers addObject:timer]; //'timers' is a property of the AppDelegate
}
-(void)fireTimer:(NSTimer *)timer {
NSLog(#"Timer Fired");
NSMutableDictionary *dict = [timer userInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:[dict objectForKey:#"notificationName"] object:nil];
}
When I setup these timers the very first time the app loads the timer is setup correctly as the NSLog output below shows but does not repeat despite clearly being set to repeat. Each timer fires once then does not continue.
2013-04-03 11:12:53.541 Test[2635:752b] TIMER REPEATS VALUE: 1
2013-04-03 11:12:53.542 Test[2635:752b] INTERVAL: 10.000000
2013-04-03 11:12:53.543 Test[2635:752b] Should Repeat
2013-04-03 11:12:53.544 Test[2635:752b] Timer Fired
The strange thing happens when I close the app and then re-open it, the timers are created and actually fire repeatedly. This app is also a universal iPhone and iPad app and I only seem to get this behaviour when running the app on iPhone. I really am stumped by this and any help is appreciated.
Edit
As requested the code to cancel the timers:
-(void)viewWillDisappear:(BOOL)animated{
NSArray *viewControllersSet = self.navigationController.viewControllers;
if (viewControllersSet.count > 1 && [viewControllersSet objectAtIndex:viewControllersSet.count-2] == self) {
// View is disappearing because a new view controller was pushed onto the stack
NSLog(#"New view controller was pushed");
} else if ([viewControllersSet indexOfObject:self] == NSNotFound) {
// View is disappearing because it was popped from the stack
NSLog(#"View controller was popped");
for(int i = 0; i < [[appDelegate timers] count]; i ++){
NSTimer *timer = [[appDelegate timers] objectAtIndex:i];
[timer invalidate];
timer = nil;
}
[[appDelegate timers] removeAllObjects];
}
}
If an NSTimer does not fire after being created with scheduledTimerWithTimeInterval, it usually means that it has not been attached to the correct runloop. Could it be that your method setupNotificationTimer() is called with different runloops during initialization and when returning to the foreground? (i.e. in the context of a different thread or similar)
This code would work around the problem:
NSTimer* timer = [NSTimer timerWithTimeInterval:[[timerDetails objectForKey:#"interval"] floatValue]
target:self
selector:#selector(fireTimer:)
userInfo:timerDetails
repeats:repeat];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
This ensures that your timers are scheduled on the main runloop and will fire.
I'm setting up a timer to fire at an interval of 13 mins. But the timer seems to be getting fired at irregular intervals. This is an intermittent issue. Some users reported the issue and I'm not able to reproduce the same.
- (void)resetIdleTimer
{
if (_idleTimer)
{
[_idleTimer invalidate];
[_idleTimer release];
_idleTimer = nil;
}
_idleTimer = [[NSTimer scheduledTimerWithTimeInterval:13*60.0
target:self
selector:#selector(idleTimerExceeded)
userInfo:nil
repeats:NO] retain];
}
- (void)idleTimerExceeded
{
// do some processing
[_idleTimer release];
_idleTimer = nil;
}
Timer will be reset (resetIdleTimer) depending on some conditions, but that's anyhow resets the timer with 13 mins.
When I looked into code, I can see only the issue that is not passing timer parameter to the selector. I'm not sure whether that's the reason for this issue or not ? Did anyone come across this kind of weird ness ? (Any how I will be updating the code to to have timer as an argument).
One user reported that its just happened after 4 mins itself.
Let confirm one thing,not passing timer parameter to the selector will not cause any issues.
Here is the updated code which works fine
#property(nonatomic,strong)NSTimer *myTimer;
or
#property(nonatomic,retain)NSTimer *myTimer;
Methods
- (void)resetIdleTimer
{
if ([self.myTimer isValid])
[self.myTimer invalidate];
self.myTimer = [NSTimer scheduledTimerWithTimeInterval:13*60.0
target:self
selector:#selector(idleTimerExceeded)
userInfo:nil
repeats:NO];
}
- (void)idleTimerExceeded
{
NSLog(#"tiggered");
}
i don't find any error in your code, the only idea about is that a user enter your class creating an instance, let's call it instance "A", then start the timer, then exit your class before 13 minutes, then enter again with a new instance "B", a new timer starts...
at this point user expects your timer methods fires after 13 minutes... but timer of "A" is still pending (because there may be an error in the way you close your class) and executes...
You may try to add some logs and try to do as i said, and check all logs (in particular if you enter the dealloc, and that the log in idleTimerExceeded print instances equals to the last instances printed in resetIdleTimer):
-(void)resetIdleTimer {
if (_idleTimer)
{
[_idleTimer invalidate];
[_idleTimer release];
_idleTimer = nil;
}
NSLog(#"...resetIdleTimer: instance: %p", self);
_idleTimer = [[NSTimer scheduledTimerWithTimeInterval:13*60.0
target:self
selector:#selector(idleTimerExceeded)
userInfo:nil
repeats:NO] retain];
NSLog(#"...resetIdleTimer: _idleTimer: %p", _idleTimer);
}
- (void)idleTimerExceeded
{
// do some processing
NSLog(#"...idleTimerExceeded: instance: %p", self);
NSLog(#"...idleTimerExceeded: _idleTimer: %p", _idleTimer);
[_idleTimer release];
_idleTimer = nil;
}
-(void) dealloc
{
NSLog(#"...dealloc: instance: %p", self);
[super dealloc];
}
just an idea... but you may have forgotten that timers retain the instance passed in target:parameter (you pass "self", so your class instance is retained), so a common error is to forget to close the timer when closing the class instance.
a correct way should be:
-a root class (AAA) instantiate your class (BBB) with timer
-BBB start its timer
-AAA wanna dealloc/resetIdleTimer class BBB: before to release it must stop/invalidate BBB.timer (if not timer retain BBB, and BBB won't be released until timer fires/executes its method)
so, if it's your case... add a method that could be called from class AAA that stop/invalidate the timer, something like:
-(void)stopTimer{
if (_idleTimer)
{
[_idleTimer invalidate];
[_idleTimer release];
_idleTimer = nil;
}
}
If you want your timer to fire in exact 13 minutes, maybe this will help:
https://stackoverflow.com/a/3519913/1226304
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.
I'm trying to do something basic to understand threads with a counter that just increments when a button is pressed, and every time the button is pressed, a new thread incrementing the same counter starts. Then I have a stop button to stop a thread that is running. How can I tell how many threads, or which thread is running? Here is my basic template I am working on. Thanks.
-(int)count {
return count;
}
-(void)setCount:(int) value {
count = value;
}
-(void)updateDisplay {
countLabel = [NSString stringWithFormat:#"%i", count];
count++;
}
-(void)myThread {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(updateDisplay)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(void)startThread {
[self performSelectorInBackground:#selector(myThread) withObject:nil];
}
-(void)myThreadStop {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(updateDisplay)
withObject:nil
waitUntilDone:NO];
[pool release];
}
-(void)stopThread {
[self performSelectorInBackground#selector(myThreadStop) withObject:nil];
}
Basically, you want to keep track of the number of threads you have running, and also assign each thread a unique ID. Assuming that startThread is an event handler for your button, you might have something like:
static int threadIndex = 0;
static int threadsRunning = 0;
-(void)startThread {
NSNumber* threadId = [NSNumber numberWithInt:threadIndex++];
threadsRunning++;
[self performSelectorInBackground:#selector(myThread) withObject:threadId];
}
Then when you stop a thread, you just decrement threadsRunning.
Looking at your code, though, I'm confused by your stopTread method, since it seems to be doing the exact same thing as the myThread method, i.e., not stopping the thread at all.
You're performing stuff in the background, which is distinct from explicitly creating threads (e.g. it might reuse threads in a thread pool).
If you want really inefficient threading code, you could use something like this:
NSThread * thread = [[[NSThread alloc] initWithTarget:self selector:#selector(myThread) object:nil] autorelease];
[thread start];
while ([thread isExecuting])
{
NSLog(#"Still running");
[NSThread sleepForTimeInterval:0.1];
}
EDIT: If you're actually going to do iPhone development, I recommend looking at NSOperation/NSInvocationOperation/NSBlockOperation instead. Thread management is a real pain to get right.
all! I want to create multiple threads in my application. I' using following code for creating a thread.
This' buttonPress method where I'm creating a thread:
- (void) threadButtonPressed:(UIButton *)sender {
threadStartButton.hidden = YES;
threadValueLabel.text = #"0";
threadProgressView.progress = 0.0;
[NSThread detachNewThreadSelector:#selector(startMethod) toTarget:self withObject:nil];
}
This' where I'm calling the method for the thread:
- (void)startMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(threadMethod) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)threadMethod {
float actual = [threadProgressView progress];
threadValueLabel.text = [NSString stringWithFormat:#"%.2f", actual];
if (actual < 1) {
threadProgressView.progress = actual + 0.01;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
}
else
threadStartButton.hidden = NO;
}
This thread works properly.
But when I try to create another thread in the same class using the same method, it gets created properly, but at the method "performSelectorOnMainThread", it doesn't execute that method. Can anybody please help me?
It seems that you are trying to queue up methods to be executed on the main thread. You might want to look into an NSOperationQueue and NSOperation objects. If you want to proceed on this path, you might consider changing the repeats parameter to YES. The problem seems to be that the main thread is busy when it's being passed this message. This will cause the main thread to block. You may also consider not using a second threadMethod and calling back to the main thread, but instead wrapping the contents of threadMethod in an #synchronized(self) block. This way, you get the benefits of multi-threading (multiple pieces of code executing at the same time and thus a reactive user interface) without doing some weird stuff with the main thread.
I'm missing the context here. I see a call that creates a new thread, and then I see a call that performs a selector (calls a method) on the main thread..
As I understand, you are calling a function in a new thread (entryMethod), in which you call a method to perform on the main thread (myMethod). I do not understand the point of this, without some background info and possibly some code.
Is it possible that the main thread is busy performing the 'myMethod' function, and thus does not respond to other calls?
Why cant you do it with the same call, by replacing
-(void)startMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(threadMethod) withObject:nil waitUntilDone:NO];
[pool release];
}
with
-(void)startMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
float actual = [threadProgressView progress];
threadValueLabel.text = [NSString stringWithFormat:#"%.2f", actual];
if (actual < 1) {
threadProgressView.progress = actual + 0.01;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
}
else
threadStartButton.hidden = NO;
}
[pool release];
}