2015-05-22 3 views
2

в моем коде Мне нужно иметь таймер с длительным запуском, чтобы запускать какую-то рутину каждую первую секунду каждой минуты. Я попытался использовать System.Timers.Timer, но это не очень полезно из-за дрейфа таймера. Так что я реализовал таймер с реактивными расширений, тикает каждые 200 мс и поставить определенную логику в начале подпрограммы:C# наблюдаемый интервал пропускает тики

IObservable<Timestamped<long>> observable = Observable.Interval(TimeSpan.FromMilliseconds(200), Scheduler.NewThread).Timestamp(); 
IDisposable subscription = observable.Subscribe(x => calculator.Calculate(x.Timestamp)); 

Затем в методе Calculate:

public void Calculate(DateTimeOffset timeElapsed) 
{ 
    if (timeElapsed.Second != 1) 
    { 
     Log.Trace("Skip calc: second != 1. {0}", timeElapsed); 
     return; 
    } 
    if ((timeElapsed.LocalDateTime - lastRun).TotalSeconds < 59) 
    { 
     Log.Trace("Skip calc: interval < 60sec."); 
     return; 
    } 
    lastRun = timeElapsed.LocalDateTime; 

    var longRunningTask = new Task(() => CalcRoutine(timeElapsed), token); 
    longRunningTask.Start(); 
    //etc.. 
} 

Проблема заключается в том, что иногда без каких-либо понятных причин этот таймер пропускает около 7 тиков. В данном конкретном случае 2 последние тики отсутствуют в 7:57:00 и 7:57:01 всех вторые отсутствуют:

2015-05-22 07:56:59.1550|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00 
2015-05-22 07:56:59.3578|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00 
2015-05-22 07:56:59.5606|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00 
2015-05-22 07:56:59.7634|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00 
2015-05-22 07:56:59.9662|Skip calc: second != 1. 22.5.2015 7:56:59 +02:00 
2015-05-22 07:57:00.1534|Skip calc: second != 1. 22.5.2015 7:57:00 +02:00 
2015-05-22 07:57:00.3562|Skip calc: second != 1. 22.5.2015 7:57:00 +02:00 
2015-05-22 07:57:00.5590|Skip calc: second != 1. 22.5.2015 7:57:00 +02:00 
2015-05-22 07:57:02.1502|Skip calc: second != 1. 22.5.2015 7:57:02 +02:00 
2015-05-22 07:57:03.3671|Skip calc: second != 1. 22.5.2015 7:57:03 +02:00 

Это поведение не является правильным. Это случается случайным образом от одного до трех раз в день. Загрузка процессора в эти моменты является нормальной без всплесков. Диски тоже в порядке. Я не могу повторить это на своем компьютере. Кроме того, есть еще один пример того же приложения, у которого меньше работы, и он отлично работает. Приложения запускаются каждый день в полночь.

Что может вызвать эту проблему?

UPD: Полный код

static void Main(string[] args) 
{ 
    var calculatorReact = new Calculator(); 
    IObservable<Timestamped<long>> observable = Observable.Interval(TimeSpan.FromMilliseconds(200)).Timestamp(); 
    IDisposable subscription = observable.Subscribe(x => calculatorReact.Calculate(x.Timestamp)); 

    Console.ReadLine(); 
} 

public class Calculator 
{ 
    DateTime lastRun = DateTime.Now.AddDays(-1); 
    public void Calculate(DateTimeOffset timeElapsed) 
    { 
     //start calcuation on the 1st second of every minute 
     if (timeElapsed.Second != 1) 
     { 
      Console.WriteLine("Skip calc: second != 1. {0}", timeElapsed); 
      return; 
     } 

     if ((timeElapsed.LocalDateTime - lastRun).TotalSeconds < 59) 
     { 
      Console.WriteLine("Skip calc: interval < 60sec."); 
      return; 
     } 
     lastRun = timeElapsed.LocalDateTime; 

     var tokenSource = new CancellationTokenSource(); 
     CancellationToken token = tokenSource.Token; 
     var longRunningTask = new Task(() => { 
       Console.WriteLine("Calulating.."); 
     }, token); 
     longRunningTask.Start(); 

     } 
    } 

UPD2 Проблема заключалась в синхронизации времени на этом сервере. По некоторым внутренним причинам нам приходилось использовать наше специальное программное обеспечение, которое быстро меняет системные часы, когда оно находит разницу. Поэтому он может легко переключать время с 7:57:00 до 7:57:02.

Извините за то, что вы не торопитесь.

+1

Сигнатура для вашего '.Subscribe (...)' call не соответствует сигнатуре метода 'Calculate'. Можете ли вы предоставить код [минимальный, полный и проверенный] (http://stackoverflow.com/help/mcve), пожалуйста? – Enigmativity

+0

Извините. Это должно быть 'IDisposable subscription = observable.Subscribe (x => calculator.Calculate (x.Timestamp));' –

+0

Ваш код по-прежнему не завершен. Я пытаюсь запустить его в LINQPad, но слишком много отсутствующих элементов. Можете ли вы опубликовать код, который будет запускаться и давать результаты? – Enigmativity

ответ

0

Соответствующие значения таймера с использованием точного соответствия по своей сути ненадежны из-за непредсказуемых ошибок округления. Это почти то же самое, что и работа с числами с плавающей запятой. Вы никогда не должны делать x == 1.0. Вместо этого вы должны указать abs(x-1.0) < 0.00001. Таким образом, вы можете снять с изображения небольшие ошибки округления, введя некоторые допуски.

В вас случае, я думаю, что вы можете сделать то же самое: вместо того чтобы работать с секундами работать с миллисекундах, или, еще лучше непосредственно с клещами, и вместо того, чтобы comapring к точному значению ввести некоторые допуски

+0

Спасибо за ваш комментарий. Но я уже использую 200 миллисекундных интервалов. Поэтому у меня около 5 таймеров в секунду. –

+0

Корень проблемы заключается в том, что вы сравниваете для точного соответствия, уменьшая интервал сам по себе, не решая его – mfeingold

+0

DateTimeOffset.Second - целое, а не double. Поэтому сравнение с 1 не должно быть проблемой. Дело в том, что я регистрирую каждый шаг метода Calculate (пропустить, когда секунда! = 1, пропустить, когда интервал, так как последний расчет мал и сам вычисляется), но по неизвестной причине несколько шагов счетчика отсутствуют. В журнале нет ничего в промежутке между 7:57:00 и 7:57:02. –

Смежные вопросы