while loop with sleep(): logging works, but not UI events
Hi All, I'm trying to program a simple timer app: you enter a number of seconds, and it updates a text field every second with the number of secs remaining. The problem is that I'm not able to do anything with the UI while this is going on. Here's my code: - (IBAction)startTimer:(id)sender { [startButton setState:NSOffState]; int timeInSeconds = [((NSNumber *)[inTextField objectValue]) intValue]; while (timeInSeconds > 0) { [outTextField setStringValue:[NSString stringWithFormat:@"%d more second(s) to go", timeInSeconds]]; sleep(1); timeInSeconds--; } [outTextField setStringValue:@"Finished timing"]; NSBeep(); } What's happening is that if I enter a number and then press 'start', 'start' stays in the 'pressed' position until the loop finishes, after which it returns to the normal position. My guess is that this is stopping any other UI events from occurring. This would also explain why I'm able to log a message each time the loop iterates. But I'm still not sure why the button is staying pressed. Thoughts, anyone? Daniel ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
On Fri, Jun 27, 2008 at 8:55 PM, Daniel Richman <[EMAIL PROTECTED]> wrote: > Hi All, > > I'm trying to program a simple timer app: you enter a number of seconds, and > it updates a text field every second with the number of secs remaining. The > problem is that I'm not able to do anything with the UI while this is going > on. Here's my code: Don't loop like this. Think about what is taking place... you are looping never allowing your main thread to do anything else. Your loop is not returning out to the normal runloop which handles user input and drives your UI. Look at using NSTimer to trigger a callback that will allow you to update UI as needed. -Shawn ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
On Fri, Jun 27, 2008 at 10:55 PM, Daniel Richman <[EMAIL PROTECTED]> wrote: > What's happening is that if I enter a number and then press 'start', 'start' > stays in the 'pressed' position until the loop finishes, after which it > returns to the normal position. My guess is that this is stopping any other > UI events from occurring. This would also explain why I'm able to log a > message each time the loop iterates. > > But I'm still not sure why the button is staying pressed. Thoughts, anyone? If you're not running the event loop then the button has no chance to repaint. You absolutely cannot block the main thread like this and expect the GUI to still function. Any reason you're not using NSTimer? Along with the painting issues, this strategy also won't work 100% of the time because sleep() is only guaranteed to block for *at most* a certain number of seconds. It can exit early if it chooses to, in particular it will if you don't mask signals from being delivered on your thread, ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
On Fri, Jun 27, 2008 at 8:59 PM, Shawn Erickson <[EMAIL PROTECTED]> wrote: > On Fri, Jun 27, 2008 at 8:55 PM, Daniel Richman > <[EMAIL PROTECTED]> wrote: >> Hi All, >> >> I'm trying to program a simple timer app: you enter a number of seconds, and >> it updates a text field every second with the number of secs remaining. The >> problem is that I'm not able to do anything with the UI while this is going >> on. Here's my code: > > Look at using NSTimer to trigger a callback that will allow you to > update UI as needed. As Stephen noted... you need record the start time (or record the end time) then check current time (look at NSDate) to ensure you count out the requested amount of time. Also you should fire your timer every 0.75 of seconds (or so) to ensure your UI update is consistent/smooth. Firing every second may cause your timer to fire a little after 1 second later so your UI could skip from say 50s to 52s. -Shawn ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
On Sat, Jun 28, 2008 at 12:10 AM, Shawn Erickson <[EMAIL PROTECTED]> wrote: > Also you should fire your timer every > 0.75 of seconds (or so) to ensure your UI update is consistent/smooth. > Firing every second may cause your timer to fire a little after 1 > second later so your UI could skip from say 50s to 52s. I do not recommend this, as using 0.75s will guarantee a jittery update. Consider what happens when you start at 0 and round down to figure out what to display. You'll display 0, then 0 again. Then you'll display 1, at 1.5 seconds after start, then 2 and 3 each 0.75 seconds after the previous number. The cycle repeats with 4, which displays 1.5 seconds after 3. Firing once a second is usually just fine, so if you want to keep things simple, do that. To ensure against skipping, round to the nearest integer, which will ensure that you can be off by up to .5 seconds without skipping any numbers. Mike ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
On Sat, Jun 28, 2008 at 7:58 AM, Michael Ash <[EMAIL PROTECTED]> wrote: > On Sat, Jun 28, 2008 at 12:10 AM, Shawn Erickson <[EMAIL PROTECTED]> wrote: >> Also you should fire your timer every >> 0.75 of seconds (or so) to ensure your UI update is consistent/smooth. >> Firing every second may cause your timer to fire a little after 1 >> second later so your UI could skip from say 50s to 52s. > > I do not recommend this, as using 0.75s will guarantee a jittery > update. Consider what happens when you start at 0 and round down to > figure out what to display. You'll display 0, then 0 again. Then > you'll display 1, at 1.5 seconds after start, then 2 and 3 each 0.75 > seconds after the previous number. The cycle repeats with 4, which > displays 1.5 seconds after 3. Actually I meant to type 0.5s which is what I use in my code for this type of thing and you don't want to use the timer to track time but the actual system clock... assuming you use a simple repeating timer. Firing once every 1 second can cause you skip numbers as timer jitter walks near to a second boundary based on wall clock time assuming you use a simple repeating timer and don't round as you suggest. If you reschedule a timer every time the prior timer fires you can use a 1 second timer delay if you attempt to ensure that your current time isn't close to a second boundary of the wall clock. ..or rounding can be used (depends on how you are calculating the time, etc.) -Shawn ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
Thanks very much for this detailed explanation. I realize my mistake now; I was thinking about this in the wrong way. I eventually coded it as follows: - (IBAction)startTimer:(id)sender { timeInSeconds = [((NSNumber *)[inTextField objectValue]) intValue]; [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(checkTimer:) userInfo:nil repeats:YES]; } - (void)checkTimer:(NSTimer *)timer { if (timeInSeconds == 0) { [timer invalidate]; [outTextField setStringValue:@"Finished timing"]; } else { [outTextField setStringValue:[NSString stringWithFormat:@"%d more second(s) to go", timeInSeconds]]; NSLog(@"in loop, timeInSeconds is %d", timeInSeconds); timeInSeconds--; } } Thanks again to all. Daniel Ken Thomases wrote: On Jun 27, 2008, at 10:55 PM, Daniel Richman wrote: I'm trying to program a simple timer app: you enter a number of seconds, and it updates a text field every second with the number of secs remaining. The problem is that I'm not able to do anything with the UI while this is going on. Here's my code: - (IBAction)startTimer:(id)sender { [startButton setState:NSOffState]; int timeInSeconds = [((NSNumber *)[inTextField objectValue]) intValue]; while (timeInSeconds > 0) { [outTextField setStringValue:[NSString stringWithFormat:@"%d more second(s) to go", timeInSeconds]]; sleep(1); timeInSeconds--; } [outTextField setStringValue:@"Finished timing"]; NSBeep(); } What's happening is that if I enter a number and then press 'start', 'start' stays in the 'pressed' position until the loop finishes, after which it returns to the normal position. My guess is that this is stopping any other UI events from occurring. This would also explain why I'm able to log a message each time the loop iterates. But I'm still not sure why the button is staying pressed. Thoughts, anyone? What others have said is correct, but I wanted to provide some more explanation. Let's think about why the button is staying pressed. I'll illustrate purely using pseudocode. I'm _not_ implying that this is literally what's happening, but let's imagine that your button click is processed by code which looks something like this: while (get an event) { if (event is a mouse click on a button) { paint the button as pressed invoke the button's action method on the button's target // this calls your startTimer: method paint the button as normal } else /* ... */ } Now, can you see why the button is staying pressed? The invocation of your startTimer: method is not exiting until N seconds go by, and the button doesn't get repainted until after it does. The code in the framework isn't magical or special. Just like code that you write, it has to finish with one thing before it can go on to the next thing. In your code, you wouldn't expect the [outTextField setStringValue:@"Finished timing"] to be performed before the while loop exits. Why would you expect anything else to? Next, let's image we insert some code into the while loop, at the end of the block: while (get an event) { if (event is a mouse click on a button) /* ... same as above ... */ for each window { for each view in this window which needs to be redrawn due to a state change draw this view } } This window and view redrawing code is what is responsible for showing the changes you make to the output text field. So, by blocking inside startTimer: you're not only preventing the button from being redrawn, you're also preventing the output text field from being redrawn with its new content. That's the explanation behind what the others have told you. Unless and until your method returns, none of the other work that's waiting to be done will get done. I hope that helps. Cheers, Ken ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
One second seems to be accurate enough for my purposes, but I'll investigate using 0.5 seconds. Daniel Shawn Erickson wrote: On Sat, Jun 28, 2008 at 7:58 AM, Michael Ash <[EMAIL PROTECTED]> wrote: On Sat, Jun 28, 2008 at 12:10 AM, Shawn Erickson <[EMAIL PROTECTED]> wrote: Also you should fire your timer every 0.75 of seconds (or so) to ensure your UI update is consistent/smooth. Firing every second may cause your timer to fire a little after 1 second later so your UI could skip from say 50s to 52s. I do not recommend this, as using 0.75s will guarantee a jittery update. Consider what happens when you start at 0 and round down to figure out what to display. You'll display 0, then 0 again. Then you'll display 1, at 1.5 seconds after start, then 2 and 3 each 0.75 seconds after the previous number. The cycle repeats with 4, which displays 1.5 seconds after 3. Actually I meant to type 0.5s which is what I use in my code for this type of thing and you don't want to use the timer to track time but the actual system clock... assuming you use a simple repeating timer. Firing once every 1 second can cause you skip numbers as timer jitter walks near to a second boundary based on wall clock time assuming you use a simple repeating timer and don't round as you suggest. If you reschedule a timer every time the prior timer fires you can use a 1 second timer delay if you attempt to ensure that your current time isn't close to a second boundary of the wall clock. ..or rounding can be used (depends on how you are calculating the time, etc.) -Shawn ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/applemaillist%40mm.danielrichman.com This email sent to [EMAIL PROTECTED] ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]
Re: while loop with sleep(): logging works, but not UI events
On Sat, Jun 28, 2008 at 8:29 AM, Shawn Erickson <[EMAIL PROTECTED]> wrote: > On Sat, Jun 28, 2008 at 8:13 AM, Daniel Richman > <[EMAIL PROTECTED]> wrote: >> Thanks very much for this detailed explanation. I realize my mistake now; I >> was thinking about this in the wrong way. I eventually coded it as follows: >> >> - (IBAction)startTimer:(id)sender >> { timeInSeconds = [((NSNumber *)[inTextField objectValue]) intValue]; >> [NSTimer scheduledTimerWithTimeInterval:1.0 >>target:self >> selector:@selector(checkTimer:) >> userInfo:nil >> repeats:YES]; >> } >> >> >> - (void)checkTimer:(NSTimer *)timer >> { >> if (timeInSeconds == 0) { >> [timer invalidate]; >> [outTextField setStringValue:@"Finished timing"]; >> } else { >> [outTextField setStringValue:[NSString stringWithFormat:@"%d more >> second(s) to go", timeInSeconds]]; >> NSLog(@"in loop, timeInSeconds is %d", timeInSeconds); >> timeInSeconds--; >> } >> } > > You really shouldn't use an NSTimer to count time as you are doing > above. For example your timer can be delayed from firing because of > other work taking place on the main thread. Over a long enough period > of time (use enters a larger value for seconds) these small delays can > build up and cause you to miscount time when compared to wall clock > time. > > You should instead use something like NSDate. For example create an > NSDate instance for the finish time (consider +[NSDate > dateWithTimeIntervalSinceNow:]) in you action method and then in your > check timer method look at that date to see if you have passed it to > know if you are done. Also you would use current time and finish time > to calculate how many seconds remand. You get current time using > -[NSDate date]. > > You could also consider two timers... one firing often to cause you to > update the UI and the other firing once at the finish time. Ah found it... http://lists.apple.com/archives/cocoa-dev/2006/Nov/msg00447.html ...and you can count using a timer if you use a constant time based (see my email related to the linked email thread)... http://lists.apple.com/archives/cocoa-dev/2006/Nov/msg00494.html -Shawn ___ Cocoa-dev mailing list (Cocoa-dev@lists.apple.com) Please do not post admin requests or moderator comments to the list. Contact the moderators at cocoa-dev-admins(at)lists.apple.com Help/Unsubscribe/Update your Subscription: http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com This email sent to [EMAIL PROTECTED]