Лучший способ выполнить цикл задач в службе Windows
У меня есть метод отправки SMS-сообщений нашим клиентам, которые выглядят следующим образом:
public void ProccessSmsQueue()
{
SmsDbContext context = new SmsDbContext();
ISmsProvider provider = new ZenviaProvider();
SmsManager manager = new SmsManager(context, provider);
try
{
manager.ProcessQueue();
}
catch (Exception ex)
{
EventLog.WriteEntry(ex.Message, EventLogEntryType.Error);
}
finally
{
context.Dispose();
}
}
protected override void OnStart(string[] args)
{
Task.Factory.StartNew(DoWork).ContinueWith( ??? )
}
Итак, у меня есть некоторые проблемы:
-
Я не знаю, сколько времени требуется для запуска метода,
-
Метод может генерировать исключения, которые я хочу записать в EventLog
-
Я хочу запустить этот метод в цикле каждые 10 минут, , но только после завершения последнего завершения.
Как я могу это достичь? Я думал об использовании ContinueWith()
, но у меня все еще есть вопросы о том, как построить всю логику.
Ответы
Ответ 1
У вас должен быть метод async, который принимает CancellationToken
, поэтому он знает, когда останавливаться, вызывает ProccessSmsQueue
в блоке try-catch
и использует Task.Delay
для асинхронного ожидания до следующего раза, когда он должен запускаться:
public async Task DoWorkAsync(CancellationToken token)
{
while (true)
{
try
{
ProccessSmsQueue();
}
catch (Exception e)
{
// Handle exception
}
await Task.Delay(TimeSpan.FromMinutes(10), token);
}
}
Вы можете вызвать этот метод, когда ваше приложение запустится, и Task.Wait
возвращенная задача до существующего, чтобы вы знали, что она завершена, и не имеет никаких исключений:
private Task _proccessSmsQueueTask;
private CancellationTokenSource _cancellationTokenSource;
protected override void OnStart(string[] args)
{
_cancellationTokenSource = new CancellationTokenSource();
_proccessSmsQueueTask = Task.Run(() => DoWorkAsync(_cancellationTokenSource.Token));
}
protected override void OnStop()
{
_cancellationTokenSource.Cancel();
try
{
_proccessSmsQueueTask.Wait();
}
catch (Exception e)
{
// handle exeption
}
}
Ответ 2
Пример рабочего класса, который я использовал в Windows Services. Он поддерживает остановку "чистым" способом с помощью блокировки.
Вам просто нужно добавить свой код в DoWork, установить таймер в методе StartTimerAndWork (в миллисекундах) и использовать этот класс в своей службе.
public class TempWorker
{
private System.Timers.Timer _timer = new System.Timers.Timer();
private Thread _thread = null;
private object _workerStopRequestedLock = new object();
private bool _workerStopRequested = false;
private object _loopInProgressLock = new object();
private bool _loopInProgress = false;
bool LoopInProgress
{
get
{
bool rez = true;
lock (_loopInProgressLock)
rez = _loopInProgress;
return rez;
}
set
{
lock (_loopInProgressLock)
_loopInProgress = value;
}
}
#region constructors
public TempWorker()
{
}
#endregion
#region public methods
public void StartWorker()
{
lock (_workerStopRequestedLock)
{
this._workerStopRequested = false;
}
_thread = new Thread(new ThreadStart(StartTimerAndWork));
_thread.Start();
}
public void StopWorker()
{
if (this._thread == null)
return;
lock (_workerStopRequestedLock)
this._workerStopRequested = true;
int iter = 0;
while (LoopInProgress)
{
Thread.Sleep(100);
iter++;
if (iter == 60)
{
_thread.Abort();
}
}
//if (!_thread.Join(60000))
// _thread.Abort();
}
#endregion
#region private methods
private void StartTimerAndWork()
{
this._timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
this._timer.Interval = 10000;//milliseconds
this._timer.Enabled = true;
this._timer.Start();
}
#endregion
#region event handlers
private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (!LoopInProgress)
{
lock (_workerStopRequestedLock)
{
if (this._workerStopRequested)
{
this._timer.Stop();
return;
}
}
DoWork();
}
}
private void DoWork()
{
try
{
this.LoopInProgress = true;
//DO WORK HERE
}
catch (Exception ex)
{
//LOG EXCEPTION HERE
}
finally
{
this.LoopInProgress = false;
}
}
#endregion
}