14

I have a requirement in my project (C#, VS2010, .NET 4.0) that a particular for loop must finish within 200 milliseconds. If it doesn't then it has to terminate after this duration without executing the remaining iterations. The loop generally goes for i = 0 to about 500,000 to 700,000 so the total loop time varies.

I have read following questions which are similar but they didn't help in my case:

  1. What is the best way to exit out of a loop after an elapsed time of 30ms in C++
  2. How to execute the loop for specific time

So far I have tried using a Stopwatch object to track the elapsed time but it's not working for me. Here are 2 different methods I have tried so far:

Method 1. Comparing the elapsed time within for loop:

Stopwatch sw = new Stopwatch();
sw.Start();

for (i = 0; i < nEntries; i++) // nEntries is typically more than 500,000
{
      // Do some stuff
      ...
      ...
      ...

      if (sw.Elapsed > TimeSpan.FromMilliseconds(200))
          break;
}

sw.Stop();

This doesn't work because if (sw.Elapsed > TimeSpan.FromMilliseconds(200)) takes more than 200 milliseconds to complete. Hence useless in my case. I am not sure whether TimeSpan.FromMilliseconds() generally takes this long or it's just in my case for some reason.

Method 2. Creating a separate thread to compare time:

Stopwatch sw = new Stopwatch();
sw.Start();                    
bool bDoExit = false;
int msLimit = 200;

System.Threading.ThreadPool.QueueUserWorkItem((x) =>
{
     while (bDoExit == false)
     {
        if (sw.Elapsed.Milliseconds > msLimit)
        {
            bDoExit = true;
            sw.Stop();
         }

         System.Threading.Thread.Sleep(10);
      }

});

for (i = 0; i < nEntries; i++) // nEntries is typically more than 500,000
{
      // Do some stuff
      ...
      ...
      ...

      if (bDoExit == true)
          break;
}

sw.Stop();

I have some other code in the for loop that prints some statistics. It tells me that in case of Method 2, the for loop definitely breaks before completing all the iterations but the loop timing is still 280-300 milliseconds.

Any suggestions to break a for loop strictly with-in 200 milliseconds or less? Thanks.

Community
  • 1
  • 1
silverspoon
  • 1,085
  • 1
  • 11
  • 22

5 Answers5

9

For a faster comparison try comparing

if(sw.ElapsedMilliseconds > 200)
   break;

You should do that check in the beggining of your loop and also during the processing, ("// Do some stuff" part of the code) because it is possible, for example, that processing starts at 190 (beginning of the loop), lasts 20 and ends at 210.

You could also measure average execution time of your processing (this is approximate because it relies on average time), this way loop should last 200 milliseconds or less, here is a demo that you can put in a Main method of a Console application and easily modify it for your application:

        Stopwatch sw = new Stopwatch();
        sw.Start();

        string a = String.Empty;
        int i;
        decimal sum = 0, avg = 0, beginning = 0, end = 0;
        for (i = 0; i < 700000; i++) // nEntries is typically more than 500,000
        {
            beginning = sw.ElapsedMilliseconds;
            if (sw.ElapsedMilliseconds + avg > 200)
                break;

            // Some processing
            a += "x";
            int s = a.Length * 100;
            Thread.Sleep(19);
            /////////////

            end = sw.ElapsedMilliseconds;
            sum += end - beginning;
            avg = sum / (i + 1);

        }
        sw.Stop();

        Console.WriteLine(
          "avg:{0}, count:{1}, milliseconds elapsed:{2}", avg, i + 1,
          sw.ElapsedMilliseconds);
        Console.ReadKey();
Ivan Golović
  • 8,732
  • 3
  • 25
  • 31
  • Checking the timing at multiple places is a good idea. My code behaves much better now with "if(sw.ElapsedMilliseconds > 200)". Thanks. And yes, I already measure average time per loop. – silverspoon Jul 05 '12 at 07:45
  • I just finished testing my application after adding 'sw.ElapsedMilliseconds' bit. Even though now I am able to break the loop within specified time the results are very similar to my previous observations - sw.ElapsedMilliseconds takes long time to calculate. Hence the total loop time is about 3 seconds (instead of 280-300ms) if I let the loop continue without breaking after using 'sw.ElapsedMilliseconds'. Very strange. – silverspoon Jul 06 '12 at 05:51
  • Try testing the loop without break statement with 2 cases: in first case instead of if `(sw.ElapsedMilliseconds + avg > 200) break;` use `(sw.ElapsedMilliseconds + avg > 200){}` and in second case comment out ElapsedMilliseconds comparsion like this `//(sw.ElapsedMilliseconds + avg > 200){}`, time difference between two cases is the time overhead that is added by ElapsedMilliseconds comparsion(s). – Ivan Golović Jul 06 '12 at 07:17
4

Another option would be to use CancellationTokenSource:

CancellationTokenSource source = new CancellationTokenSource(100);

while(!source.IsCancellationRequested)
{
    // Do stuff
}
Rzv.im
  • 978
  • 7
  • 12
1

Use the first one - simple and have better chances to be precise than second one.

Both cases have the same kind of termination condition, so both should behave are more-or-less the same. Second is much more complicated due to usage of threads and Sleep, so I'd use first one. Also second one is much less precise due to sleeps.

There are abolutely no reasons for TimeSpan.FromMilliseconds(200) to take any significant amount of time (as well as calling it in every iteration).

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • Also at the second method you will have race conditions when accessing `bDoExit`. Instead you could refactor the second method and use some kind of cancellation token (http://msdn.microsoft.com/en-us/library/dd997364.aspx). They are designed for _asking_ an operation to stop. – Johannes Egger Jul 05 '12 at 07:26
  • @Jasd, I'd not call it race condition as there is only one writer. The code obviously have chance never to work without `volatile` (as it could be optimized out in loop...), but if it would it will eventually pick up change in the boolean variable (since it makes only single transitions false->true). – Alexei Levenkov Jul 05 '12 at 07:33
  • You are right, there can't be a race condition because reading and writing a boolean is an atomic operation. My bad. – Johannes Egger Jul 05 '12 at 07:43
1

Using cancellation token:

 var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(15)).Token;

 while (!cancellationToken.IsCancellationRequested)
 {
     //Do stuff...
 }
ArcSin2x
  • 11
  • 1
0

I don't know if this is that exactly, but I think it's worth a try using a System.Timers.Timer:

int msLimit = 200;
int nEntries = 500000;
bool cancel = false;

System.Timers.Timer t = new System.Timers.Timer();
t.Interval = msLimit;
t.Elapsed += (s, e) => cancel = true;
t.Start();

for (int i = 0; i < nEntries; i++)
{
    // do sth

    if (cancel) {
        break;
    }
}
Johannes Egger
  • 3,874
  • 27
  • 36