Синхронизация сервиса с таймером

Я пытаюсь написать службу на С #, которая должна запускаться в заданном интервале (тайм-аут) с заданной даты. Если дата находится в будущем, служба должна дождаться запуска до наступления даты и времени.

Пример:

  • Если я установил тайм-аут на 1 час с 21:00:00, я хочу, чтобы программа запускалась каждый час.

  • Если я установил тайм-аут на 1 час с 3999.01.01 21:00:00, я хочу, чтобы программа работала до даты, а затем запускалась каждый час

Я вроде как добился этого с помощью следующего кода, но у него есть некоторые проблемы!

  1. Когда я устанавливаю службу (с помощью installutil), она помечается как запускается из-за «Thread.Sleep ()». Эта служба, кажется, зависает и «устанавливается», пока не запустится.

  2. Код внутри ServiceTimer_Tick () может занять больше времени, чем ожидаемый тайм-аут. Как я могу предотвратить увеличение стека таймера, если это произойдет?

Альтернативы, о которых я подумал:

  • включить использование timeout.Interval в первый раз, а затем сбросить его последующие вызовы, но это кажется неправильным.

  • Я также подумал о том, чтобы отказаться от идеи сервиса в целом, скомпилировать его как исполняемый файл и настроить запланированные задачи.

Сокращенный пример:

public Service()
{
    _timeout = new TimeSpan(0,1,0,0);
    _timer = new System.Timers.Timer();
    _timer.Interval = _timeout.TotalMilliseconds;
    _timer.Elapsed += new ElapsedEventHandler(ServiceTimer_Tick);            
}

private void ServiceTimer_Tick(object sender, System.Timers.ElapsedEventArgs e)
{            
    lock (_obj)  
    { 
        // Stuff that could take a lot of time
    }
}

public static void Main()
{
    Run(new Service());
}

protected override void OnStart(string[] args)
{
    long current = DateTime.Now.Ticks;
    long start = new DateTime(2010,9,15,21,0,0).Ticks;
    long timeout = _timeout.Ticks;
    long sleep;

    if (current > start)
        sleep = timeout - ((current % timeout)) + (start % timeout);
    else
        sleep = start - current;

    Thread.Sleep(new TimeSpan(sleep));

    _timer.AutoReset = true;
    _timer.Enabled = true;
    _timer.Start();
}

person Makach    schedule 24.09.2010    source источник
comment
Я действительно не понимаю, чего вы пытаетесь достичь. Год 3999? Что делает тайм-аут? .... Это может быть связано с: Как сгенерировать событие в определенное время в C #?   -  person dtb    schedule 25.09.2010
comment
Я просто установил дату / год, которые, очевидно, будут в будущем - вместо этого это сбило вас с толку и, вероятно, многих других.   -  person Makach    schedule 25.09.2010


Ответы (3)


вам нужно переместить логику таймера в отдельный поток, который вы создаете из своей подпрограммы OnStart. Тогда ваша логика не сможет вмешиваться в SCM, и служба запустится нормально.

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

Процедура Run этого потока могла бы выглядеть так:

    public void run()
    {
        while (processing)
        {
            //initiate action on every full hour
            if (DateTime.Now.Second == 0 && DateTime.Now.Minute == 0)
            {
                //Do something here
                DoSomething();
                //Make sure we sleep long enough that datetime.now.second > 0
                Thread.Sleep(1000);
            }
            Thread.Sleep(100);
        }
    }
person BrokenGlass    schedule 24.09.2010
comment
Это окончательно движет меня в правильном направлении .. Вы читаете мой вопрос :) - person Makach; 25.09.2010
comment
Проверять каждые 100 миллисекунд, достигнут ли полный час, - это движение в неправильном направлении. Ожидание - это работа таймера. - person dtb; 25.09.2010
comment
вы не можете просто подождать 5 часов и предположить, что часы не были скорректированы / настроены в среднем, в конце концов, вас интересует время на часах, а не время истечения вашего таймера. - person BrokenGlass; 25.09.2010
comment
Планировщик Windows учтет все эти важные моменты. Изобретать колесо здесь, вероятно, не лучшая идея. - person dtb; 25.09.2010

Это проще с System.Threading.Timer. Вы можете указать, как долго ждать до первого тика, а затем, как часто ставить галочку после этого.

Итак, если вы хотите подождать 2 дня перед запуском, а затем делать что-то один раз в час, вы должны написать:

Timer MyTimer = new Timer(TimerCallback, null, TimeSpan.FromHours(48), TimeSpan.FromHours(1));

Тем не менее, если это то, что нужно запускать только один раз в час, то похоже, что вам действительно нужен исполняемый файл, который вы затем планируете с помощью Планировщика задач Windows.

person Jim Mischel    schedule 24.09.2010

Вы можете использовать System.Threading.Timer. Он поддерживает как dueTime, так и period, что как раз то, что вам нужно.

person Hans Passant    schedule 24.09.2010