Martin
Martin

Reputation: 3472

How to measure elapsed time using DateTime class?

I am encountering a strange behavior here. Control never comes out of the do-while loop. On debugging i found that the value of the variable interval decreases during some of the loops, instead of increasing! I'm missing something obvious here, but can't figure it out.

static void Main(string[] args)
{
    int interval = 0;
    DateTime startTime = DateTime.Now;

    //Task1(); //Task1 can take 4-8 seconds to complete

    do
    {
        Thread.Sleep(100);
        interval = (DateTime.Now - startTime).Milliseconds;
    } while (interval < 10000); //wait for at-least ten seconds from program start, before executing task2

    //Task2();
}

Upvotes: 8

Views: 12420

Answers (4)

Matt Davis
Matt Davis

Reputation: 46034

I created the following struct as a simple-to-use timer as an alternative to System.Diagnostics.Stopwatch. The Stopwatch class works just fine and truly does provide high-precision interval checking on the Windows systems I use. In some cases, though, I prefer the simpler API offered by EasyTimer over having to start/restart/stop the Stopwatch. This is especially true when I don't need to be highly accurate with the interval, just in-the-ballpark. As a value type, it doesn't add any pressure to the garbage collector, and it's slightly more efficient (as a micro optimization) to check the interval with EasyTimer than it is with Stopwatch.

public struct EasyTimer
{
    /// <summary>
    /// Gets a value indicating whether or not the time interval has elapsed.
    /// </summary>
    /// <remarks>
    /// If the time interval has elapsed, the timer is reset to check the next time interval.
    /// </remarks>
    public bool HasElapsed
    {
        get
        {
            var nowTicks = DateTime.UtcNow.Ticks;
            if (nowTicks - this.startTicks < this.intervalTicks) { return false; }
            this.startTicks = DateTime.UtcNow.Ticks;
            return true;
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="EasyTimer"/> structure with the
    /// time interval to check.
    /// </summary>
    /// <param name="intervalInMilliseconds">
    /// The time interval to check in milliseconds.
    /// </param>
    public EasyTimer(int intervalInMilliseconds)
    {
        this.intervalTicks = TimeSpan.FromMilliseconds(intervalInMilliseconds).Ticks;
        this.startTicks    = DateTime.UtcNow.Ticks;
    }

    private          long startTicks;
    private readonly long intervalTicks;
}

Here's an example of what I use it for. Consider...

using System.Threading;
private ManualResetEvent shutdownEvent = new ManualResetEvent(false);

// Allow external user to signal work to stop.
public void Shutdown()
{
    this.shutdownEvent.Set();
}

private void DoWork()
{
    // This approach requires checking the event each time through the loop,
    // which involves overhead I wish to minimize.
    while (!this.shutdownEvent.WaitOne(0))
    {
        // Do some ongoing work here...
    }
}

Here's an approach that minimizes the overhead using Stopwatch, but note the additional complexity.

using System.Threading;
private ManualResetEvent shutdownEvent = new ManualResetEvent(false);

// Allow external user to signal work to stop.
public void Shutdown()
{
    this.shutdownEvent.Set();
}

private void DoWork()
{
    var sw = Stopwatch.StartNew();
    while (true)
    {
       // Do some ongoing work here...

       // Check the ShutdownEvent every second.
       if (sw.ElapsedMilliseconds >= 1000)
       {
           if (this.shutdownEvent.WaitOne(0)) { break; }
           sw.Restart();
       }
    }
}

Finally, here's an approach using EasyTimer.

using System.Threading;
private ManualResetEvent shutdownEvent = new ManualResetEvent(false);

// Allow external user to signal work to stop.
public void Shutdown()
{
    this.shutdownEvent.Set();
}

private void DoWork()
{
    var et = new EasyTimer(1000);
    while (true)
    {
       // Do some ongoing work here...

       // Check the ShutdownEvent every second (or so).
       if (et.HasElapsed && this.shutdown.WaitOne(0)) { break; }
    }

    // or alternatively...
    // while (!(et.HasElapsed && this.shutdownEvent.WaitOne(0))
    // {
    //     // Do some ongoing work here...
    // }
}

Upvotes: 3

Fabio
Fabio

Reputation: 32445

Don't use DateTime for measuring time intervals.

Use Stopwatch class, which was designed exactly for this purpose

var clock = new Stopwatch();
clock.Start();
do
{
    // do something
}
while (clock.ElapsedMilliseconds < 10000)
clock.Stop();

Note: Of course you can use DateTime for measuring time, but if you need precision less then second then Stopwatch is right tool for the job.
And Stopwatch have much more readable and easy to use methods for measuring time

In your particular case: "wait for at-least ten seconds from program start, before executing task2" you can use asynchronous approach

var task1 = StartTask1();

await Task.Delay(10000); // next line will be executed approximately after 10 s

var task2 = StartTask2();

Upvotes: 16

waka
waka

Reputation: 3407

With TimeSpan you always have to use the Total properties if you want to know the total time that has passed. All properties without Total in front of them only display the current amount of time that has passed.

For example: A method runs for 1 minute and 7 seconds.

Console.WriteLine(ts.Seconds);

Output:

7

Why is that? Because there are only 60 seconds in a minute and ts.Seconds will start increasing again from 0 to 59.

Compare this to

Console.WriteLine(ts.TotalSeconds),

Output:

67

Now we have the total amount of seconds that have passed, in this case 67 seconds.

Upvotes: 3

Sefe
Sefe

Reputation: 14007

Your problem is that you measure the milliseconds part of the TimeSpan. You have to use TotalMilliseconds instead of Milliseconds:

do
{
    Thread.Sleep(100);
    interval = (DateTime.Now - startTime).TotalMilliseconds;
} while (interval < 10000); //wait for at-least ten seconds from program start, before executing task2

Upvotes: 12

Related Questions