Search code examples
c#winforms.net-4.0timercountdown

Remaining time based on progress?


I would like to output the time remaining for a process on the following format:

d hh:mm:ss

When the process start, it sets a DateTime startTimevariable with the current time.

From there I have timer on my GUI that updates a label every second and here is what I have to calculated the remaining time:

TimeSpan timeRemaining = TimeSpan.FromTicks(DateTime.Now.Subtract(startTime).Ticks * (iProgress.Maximum - iProgress.Value) / iProgress.Value);

And using on the label as:

labelTimeRemaining.Text = timeRemaining.ToString(@"d\ hh\:mm\:ss");

However the time variation is displayed is going on very oddly jumping several minutes with getting into a steady mode.

I know it should jump on the begin but it should stabilize at some point right ?

I process about 1~3 records per second however the time remaining is jumping 10~20 minutes every update.

What am I doing wrong or how could I fix this issue ?

Let me know if u need more info or code.

UPDATE

Below is portion of the code I am using:

private Stopwatch _timer = new Stopwatch();
private int _total = 0;
private int _current = 0;

Start a thread or task with:

private void MyTask()
{
    _timer.Reset();
    _timer.Start();
    for (int i = 0; i < _total; i++)
    {
        _current++;
        Thread.Sleep(500);
    }
    _timer.Stop();
}

Create a winforms Timer with the below function to run every second:

if (_timer.IsRunning && _current > 0)
{
    float elapsedMin = ((float)_timer.ElapsedMilliseconds / 1000) / 60;
    float minLeft = (elapsedMin / _current) * (_total - _current);
    TimeSpan eta = TimeSpan.FromMinutes(minLeft);
    iStatus.Text = eta.ToString(@"d\ hh\:mm\:ss");
}

Solution

  • Based on the comments you provided, when you are working with 10K to 10M records, that a fluctuation is rather big as you could see. Here is the test example that will show you the fluctuation.

    DateTime dt = DateTime.Now;
    Thread.Sleep(3000);
    TimeSpan ts = TimeSpan.FromTicks(DateTime.Now.Subtract(dt).Ticks * (100000 - 7) / 7);
    Debug.WriteLine(ts.ToString());
    Thread.Sleep(1000);
    ts = TimeSpan.FromTicks(DateTime.Now.Subtract(dt).Ticks * (100000 - 9) / 9);
    Debug.WriteLine(ts.ToString());
    

    Also, even if you take care of integer division

    DateTime dt = DateTime.Now;
    Thread.Sleep(3000);
    TimeSpan ts = TimeSpan.FromTicks((long)((double)DateTime.Now.Subtract(dt).Ticks * (100000.0 - 7.0) / 7.0));
    Debug.WriteLine(ts.ToString());
    
    Thread.Sleep(1000);
    ts = TimeSpan.FromTicks((long)((double)DateTime.Now.Subtract(dt).Ticks * (100000.0 - 9.0) / 9.0));
    Debug.WriteLine(ts.ToString());
    

    But if you could normalize your records number to percents. Let's say you have 100K records, 1K is one percent and that is done in approximately 1000/2/60 around 8 minutes. After 32 minutes you will have 4% done. After another you will have 6% done and after another 24 minutes you will have around 9% done but lets say 10%. After more 30 minutes you should have 14% done. And now, if you update your textBox rarely the users will have the illusion that you really know how long it will take, and you will end up changing time for hour or two, but do it on every minute or on every percent changed (have you ever copied 30GB on Windows?)

    DateTime dt1 = DateTime.Now;
    DateTime dt2 = DateTime.Now;
    dt2 = dt2.AddMinutes(32);
    ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 4) / 4);
    Debug.WriteLine(ts.ToString());
    
    dt2 = dt2.AddMinutes(16);
    ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 6) / 6);
    Debug.WriteLine(ts.ToString());
    
    dt2 = dt2.AddMinutes(24);
    ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 10) / 10);
    Debug.WriteLine(ts.ToString());
    
    dt2 = dt2.AddMinutes(30);
    ts = TimeSpan.FromTicks(dt2.Subtract(dt1).Ticks * (100 - 14) / 14);
    Debug.WriteLine(ts.ToString());