Ожидание завершения истекшего события таймера до закрытия/остановки приложения/службы
Резюме:
В приложении Windows и приложении консоли я звоню в общую библиотеку, которая содержит таймер, который периодически запускает действие, которое занимает около 30 секунд. Это прекрасно работает, однако...
Когда вызывается останов службы или выход приложения, и таймер находится в ElapsedEventHandler, мне нужно, чтобы остановка/приложение службы прекращалось до завершения обработчика события.
Я реализовал эту функциональность, имея свойство Boolean InEvent, которое проверяется при вызове метода остановки таймера.
Пока это функционально, возникает вопрос: является ли это лучшим способом сделать это? Существует ли альтернативный подход, который может служить этой цели лучше?
Другая проблема заключается в том, что мне нужно избегать запроса на остановку службы, если "Служба не ответила на запрос остановки"
Это моя реализация
public sealed class TimedProcess : IDisposable
{
static TimedProcess singletonInstance;
bool InEvent;
Timer processTimer;
private TimedProcess()
{
}
public static TimedProcess Instance
{
get
{
if (singletonInstance == null)
{
singletonInstance = new TimedProcess();
}
return singletonInstance;
}
}
public void Start(double interval)
{
this.processTimer = new Timer();
this.processTimer.AutoReset = false;
this.processTimer.Interval = interval;
this.processTimer.Elapsed += new ElapsedEventHandler(this.processTimer_Elapsed);
this.processTimer.Enabled = true;
}
public void Stop()
{
if (processTimer != null)
{
while (InEvent)
{
}
processTimer.Stop();
}
}
void processTimer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
InEvent = true;
// Do something here that takes ~30 seconds
}
catch
{
}
finally
{
InEvent = false;
processTimer.Enabled = true;
}
}
public void Dispose()
{
if (processTimer != null)
{
Stop();
processTimer.Dispose();
}
}
}
И вот как он вызывается в главном приложении OnStart/console:
TimedProcess.Instance.Start(1000);
Так вызывается в сервисе OnStop и main приложения (ожидает нажатия):
TimedProcess.Instance.Stop();
Ответы
Ответ 1
Вероятно, самый простой и надежный способ - использовать Monitor
. Создайте объект, к которому может обратиться основная программа и обратный вызов таймера:
private object _timerLock = new object();
Ваша основная программа пытается заблокировать это до выключения:
// wait for timer process to stop
Monitor.Enter(_timerLock);
// do shutdown tasks here
И ваш обратный вызов по таймеру также блокирует его:
void processTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (!Monitor.TryEnter(_timerLock))
{
// something has the lock. Probably shutting down.
return;
}
try
{
// Do something here that takes ~30 seconds
}
finally
{
Monitor.Exit(_timerLock);
}
}
Основная программа никогда не должна отпускать блокировку после ее получения.
Если вы хотите, чтобы основная программа продолжала работу и закрывалась через некоторое время, независимо от того, получила ли она блокировку, используйте Monitor.TryEnter
. Например, это будет ждать 15 секунд.
bool gotLock = Monitor.TryEnter(_timerLock, TimeSpan.FromSeconds(15));
Возвращаемое значение true
, если оно удалось получить блокировку.
Кстати, я настоятельно рекомендую использовать System.Threading.Timer
, а не System.Timers.Timer
. Последний сквозит исключения, которые могут в конечном итоге скрывать ошибки. Если в событии Elapsed
возникает исключение, оно никогда не исчезнет, а это значит, что вы никогда не знаете об этом. Дополнительную информацию см. В сообщении в блоге.
Ответ 2
ИЗМЕНИТЬ
Каждый обратный вызов System.Timers.Timer
помещается в очередь на ThreadPool
. Имейте в виду, что System.Timers.Timer
может иметь состояние гонки (вы можете прочитать больше об этом здесь.) System.Threading.Timer
- немного более приятная оболочка, которая Я предпочитаю использовать из-за этого простоту.
Вы не описали достаточно подробностей, чтобы узнать, может ли ваше конкретное приложение справиться с этим состоянием гонки, поэтому трудно сказать. Но, учитывая ваш код, возможно, что после < Stop()
может возникнуть обратный вызов для processTimer_Elapsed
.
Для проблемы с таймаутом службы -
Один из способов сделать это - сделать вызов метода ServiceController
WaitForStatus
с таймаутом. Я делал это в прошлом, и он работает достаточно хорошо, хотя я помню, что в течение очень долгого времени были случаи с краем.
См. ссылку MSDN. Пример использования здесь.
Ответ 3
Одна из возможных альтернатив, похоже, заключается в том, чтобы не выполнять фактическую работу в самом обратном вызове таймера, а просто чтобы опустить в очередь рабочий элемент из пула протектора для выполнения работы. Затем вы можете пойти и утилизировать таймер - все, что в настоящее время работает в пуле потоков, останется в рабочем состоянии, и ваша служба может немедленно ответить на запрос остановки, но элемент пула потоков (если он поставлен в очередь) все равно будет обработан.