Выйти из цикла через определенное время в С#

У меня есть требование в моем проекте (С#, VS2010,.NET 4.0), что конкретный цикл for должен завершиться в течение 200 миллисекунд. Если это не так, оно должно завершиться после этой продолжительности без выполнения оставшихся итераций. Обычно петля имеет значение i = 0 примерно от 500 000 до 700 000, поэтому общее время цикла изменяется.

Я прочитал следующие вопросы, которые похожи, но они не помогли в моем случае:

До сих пор я пытался использовать объект Stopwatch для отслеживания прошедшего времени, но он не работает для меня. Вот два разных метода, которые я пробовал до сих пор:

Метод 1. Сравнение прошедшего времени в цикле for:

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();

Это не работает, потому что if (sw.Elapsed > TimeSpan.FromMilliseconds(200)) занимает более 200 миллисекунд. Следовательно, бесполезно в моем случае. Я не уверен, что TimeSpan.FromMilliseconds() обычно занимает это долгое время, или это только в моем случае по какой-то причине.

Способ 2. Создание отдельного потока для сравнения времени:

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();

У меня есть другой код в цикле for, который печатает некоторые статистические данные. Он сообщает мне, что в случае метода 2 цикл for определенно прерывается до завершения всех итераций, но время цикла составляет 280-300 миллисекунд.

Любые предложения по разрыву цикла for строго с-в 200 миллисекундах или меньше? Спасибо.

Ответ 1

Для более быстрого сравнения попробуйте сравнить

if(sw.ElapsedMilliseconds > 200)
   break;

Вы должны выполнить эту проверку в начале цикла, а также во время обработки (часть кода "//Do some stuff" ), поскольку возможно, например, что обработка начинается с 190 (начало цикл), длится 20 и заканчивается на 210.

Вы также можете измерить среднее время выполнения вашей обработки (это примерно, потому что оно зависит от среднего времени), этот цикл цикла должен длиться 200 миллисекунд или меньше, вот демонстрация, которую вы можете использовать в основном методе консоли приложения и легко изменить его для вашего приложения:

        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();

Ответ 2

Использовать первый - простой и иметь лучшие шансы быть точным, чем второй.

Оба случая имеют одно и то же условие завершения, поэтому оба должны вести себя более или менее одинаковыми. Во-вторых, гораздо сложнее из-за использования потоков и сна, поэтому я бы использовал первый. Кроме того, второй из них гораздо менее точен из-за сна.

Нет никаких причин для TimeSpan.FromMilliseconds(200) принимать какое-либо значительное количество времени (а также называть его на каждой итерации).

Ответ 3

Другой вариант - использовать CancellationTokenSource:

CancellationTokenSource source = new CancellationTokenSource(100);

while(!source.IsCancellationRequested)
{
    // Do stuff
}

Ответ 4

Я не знаю, так ли это, но я думаю, что стоит попробовать использовать 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;
    }
}