1

I've got a very simple program written in C#, but the loop never exits because the times don't match.

static void Main(string[] args)
{
    while (System.DateTime.Now != new System.DateTime(2011, 05, 23, 22, 17, 0))
    {
    }
    System.Diagnostics.Process.Start(file);   
}

The idea is that when the time ticks over to the specified time, then the given file will be started. However, I've tested this program with values which are, for example, just one minute ahead of the current time as reported by Windows, and it won't start the process. I've verified that the Process.Start call is correct. Any suggestions?

Edit: No, this is not an experiment or anything of the sort. It's because I keep turning off my alarm clocks in my sleep. file is an mp3 file, and I'm going to leave my speakers on, and I'm pretty sure that I don't possess the capacity to deal with that in my sleep. First ever practical problem I solved with a program. As it possesses a rather specific purpose, I think you'll agree that the necessity of another solution is, well, limited.

Edit: I didn't realize that the DateTime type went down to that kind of precision, else I would have spotted this myself. I thought that they were only valid down to the second, and since the loop should run even in debug mode in the IDE many, many times a second, I didn't see why an exact match would be unreasonable. But, of course, if you're comparing it down to the hundred nanoseconds, it's pretty damn unlikely.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • @DeadMG: Considering that can be done in ~8 lines of C++ code, total, I'm wondering why you chose to do it in C# if not as an experiment. – Ben Voigt May 23 '11 at 21:38
  • It will only exist for one millisecond (or whatever mesure the dateTime use). – Automatico May 23 '11 at 21:39
  • @Ben Voigt: I don't believe that I have any C++ libraries that would perform such a task at hand, whereas I know for a fact that .NET does provide. – Puppy May 23 '11 at 21:40
  • @DeadMG: `CreateWaitableTimer`, `SetWaitableTimer`, `WaitForSingleObject`, `ShellExecute`. All core Win32 API functions. – Ben Voigt May 23 '11 at 21:41
  • 2
    The accuracy of the clock is actually about 1/64th of a second. Which is plenty big enough to miss since that's about the thread quantum; some other thread could be running for that 1/64th of a second. – Eric Lippert May 23 '11 at 21:43
  • @Ben Voigt: SetWaitableTimer doesn't take a time and date, it takes a number of 100ns intervals after which to activate. If I wanted to use it, I'd have to compute that value. And I couldn't get the file to open with ShellExecute- actually tried that before Process.Start. – Puppy May 23 '11 at 21:46
  • @DeadMG: `SystemTimeToFileTime` to convert a date (year/month/day style) to the format used by `SetWaitableTimer`. We may now be needing more than 8 lines of C++. And I find it surprising that `ShellExecute` didn't work, since that's what the static `Process.Start` method uses. – Ben Voigt May 23 '11 at 21:53
  • @Ben Voigt: Yeah, me too. Could be that I just messed up the file and directory parameters, but it doesn't really matter. And I have definitely never seen SystemTimeToFileTime before, else I think I would have gone to C++ for this. – Puppy May 23 '11 at 21:55
  • @DeadMG: If you're still on the fence, let me point out also that `SetWaitableTimer` can resume a system from ACPI sleep at the specified time. – Ben Voigt May 23 '11 at 21:57
  • I would expect anyone who is looking for an alarm clock MP3 player to immediately try Window's Task Scheduler. :) – Jeffrey L Whitledge May 24 '11 at 15:04

6 Answers6

4

You should do

static void Main(string[] args)
{
    while (System.DateTime.Now < new System.DateTime(2011, 05, 23, 22, 17, 0))
    {
    }
    System.Diagnostics.Process.Start(file);   
}

Since if you don't tick on that exact time it isn't going to ever exit the while

Edit long explanation of why != most likely won't work So actually you could write the code like this:

static void Main(string[] args)
{
    DateTime fireDate = new DateTime(2011, 05, 23, 22, 17, 0);

    while (System.DateTime.Now < fireDate)
    {
    }
    System.Diagnostics.Process.Start(file);   
}

As Ben Voigt pointed out DateTime comparisons looks at the Ticks property on a DateTime DateTime.Ticks which is 1/10,000 of a millisecond.

Your loop probably doesn't execute that frequently.

msarchet
  • 15,104
  • 2
  • 43
  • 66
  • This was the correct answer. I would have expected that the tick rate of the loop would be vastly in excess of what would be required to hit the match, but this works too. – Puppy May 23 '11 at 21:28
  • @DeadMG I gave some more insight on why an exact match won't work for you. – msarchet May 23 '11 at 21:33
  • 1
    @msarchet: No, millisecond isn't the lowest resolution of DateTick, and `operator !=` doesn't use it. It directly compares the `InternalTicks` field (source of the [`Ticks` property](http://msdn.microsoft.com/en-us/library/system.datetime.ticks)), ticks are 10,000 times smaller than a millisecond. – Ben Voigt May 23 '11 at 21:36
  • @Ben Voigt ah, I just quickly looked at DateTime. adjusting my answer to reflect your added knowledge. – msarchet May 23 '11 at 21:40
  • Nitpick: This is creating a Spinlock, which is a very bad way of doing things. A better way would be a Thread.Sleep. But you pointed out the proper problem: Test for greater-than instead of equality. – Michael Stum May 23 '11 at 22:03
3

Why wouldn't you just use a sleep or create a timer?

Matthew Sanford
  • 1,069
  • 1
  • 13
  • 21
  • 1
    Answer vs comment aside (especially given that he only just barely can comment now at 50), this is the more efficient solution. A test app shows the loop holding 25% CPU until it breaks, while using a System.Threading.Timer sits at 0% CPU. See also this post about using "more robust Timer mecahnisms in .NET." http://stackoverflow.com/questions/2822441/system-timers-timer-threading-timer-vs-thread-with-whileloop-thread-sleep-for-p – David Ruttka May 23 '11 at 21:43
  • @druttka: I have no need to not block the CPU, as it's part of the use case of the application that I will not be using the machine whilst it's running. Also, I have an i7 and even if I lost a core whilst trying to use it, it would hardly degrade my experience. – Puppy May 23 '11 at 21:47
  • @DeadMG That's fine for you in this case. My comment is for any others who may come along later and want their application to perform while the user is awake :) – David Ruttka May 23 '11 at 21:48
  • 1
    @druttka: don't forget that heating up a core like that has big costs. In rack-mounted hardware most of the cost of the machine is actually air conditioning, and on laptops, the last thing you want is a core devoted to doing nothing but draining the battery as fast as possible. Maybe you don't care about these scenarios but I assure you many people do. There are better ways to wait for something than busy waiting in a tight loop. – Eric Lippert May 23 '11 at 22:40
  • @Eric Thanks, battery drain and heat are excellent points. I hope my previous comment expressed it, but I do support this answer rather than the tight loop answers, and I do care about these scenarios. The "fine for you" bit was only out of respect for DeadMG's personal freedom of choice for his alarm clock app. :) – David Ruttka May 24 '11 at 00:25
  • @DeadMG, if you're going to SpinWait it at least do it the right way with a SpinWait call (which will, where possible, use special cpu instructions that allow other things to happen on the core and reduce it's power consumption if not) – ShuggyCoUk May 24 '11 at 08:42
3

Use < instead of !=.

In any case, that's a terrible way of waiting for a specific time.

Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
0

Is this an April Fools Joke?

Use < instead of !=.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • It does look like an experiment to see what the answers are :-) – Diego Mijelshon May 23 '11 at 21:26
  • @Diego: @DeadMG is practically a C++ guru, I can't see him messing up something this simple. So it must be a social experiment as you said. – Ben Voigt May 23 '11 at 21:27
  • OP may be a C++ guru, but he doesn't seem to have any rep on the C# tag, so it's not unreasonable for him to expect it to work. – Gabe May 23 '11 at 21:31
  • @Gabe: OP is fully aware of numerical precision problems, which are no stranger to the C++ tag. – Ben Voigt May 23 '11 at 21:32
0

I would switch that to being

while (System.DateTime.Now < new System.DateTime(2011, 05, 23, 22, 17, 0))
{
}

instead. Though I'd also be very wary of using a tight loop like that and would think that setting up a scheduled task or something would be much more effective.

Tim
  • 14,999
  • 1
  • 45
  • 68
0

Change it to user the less than operator. I am a little surprised it misses the time by seconds, bBut using equality for times is generally dangerous for this very precise reason (equality based on Ticks).

System.DateTime.Now < otherDateTime

It should be noted that this type of "busy waiting" is incredibly wasteful of the processor. As others are pointing out, use a timer, or at least sleep the thread so that the processor can do something else with its time.

pickypg
  • 22,034
  • 5
  • 72
  • 84
  • I can't remember ever using equals on a non-generated `DateTime` (on both sides), which would explain why I never realized it actually compared the `Ticks`. Nice one. – pickypg May 23 '11 at 21:27
  • 1
    @Ben: The *field* has that precision, but the *clock that fills in the field* only has accuracy to 1/64th of a second on most hardware. – Eric Lippert May 23 '11 at 21:42
  • 1
    @Eric: Which means only 64/1e7 of the possible `DateTime` values will ever be reached by the system clock, explaining the failure of the original code. And some of those values probably go to other threads. – Ben Voigt May 23 '11 at 21:50