Files
UtilidadesTSL4net.c/ScheduleTimer.cs
2026-05-14 09:48:46 +02:00

254 lines
7.7 KiB
C#

/***************************************************************************
* Copyright Andy Brummer 2004-2005
*
* This code is provided "as is", with absolutely no warranty expressed
* or implied. Any use is at your own risk.
*
* This code may be used in compiled form in any way you desire. This
* file may be redistributed unmodified by any means provided it is
* not sold for profit without the authors written consent, and
* providing that this notice and the authors name is included. If
* the source code in this file is used in any commercial application
* then a simple email would be nice.
*
**************************************************************************/
using System;
using System.Collections;
using System.Timers;
using System.Diagnostics;
namespace UtilidadesCSharp.Schedule
{
/// <summary>
/// ScheduleTimer represents a timer that fires on a more human friendly schedule. For example it is easy to
/// set it to fire every day at 6:00PM. It is useful for batch jobs or alarms that might be difficult to
/// schedule with the native .net timers.
/// It is similar to the .net timer that it is based on with the start and stop methods functioning similarly.
/// The main difference is the event uses a different delegate and arguement since the .net timer argument
/// class is not creatable.
/// </summary>
public class ScheduleTimerBase : IDisposable
{
public ScheduleTimerBase()
{
_Timer = new Timer();
_Timer.AutoReset = false;
_Timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed);
_Jobs = new TimerJobList();
_LastTime = DateTime.MaxValue;
}
/// <summary>
/// Adds a job to the timer. This method passes in a delegate and the parameters similar to the Invoke method of windows forms.
/// </summary>
/// <param name="Schedule">The schedule that this delegate is to be run on.</param>
/// <param name="f">The delegate to run</param>
/// <param name="Params">The method parameters to pass if you leave any DateTime parameters unbound, then they will be set with the scheduled run time of the
/// method. Any unbound object parameters will get this Job object passed in.</param>
public void AddJob(IScheduledItem Schedule, Delegate f, params object[] Params)
{
_Jobs.Add(new TimerJob(Schedule, new DelegateMethodCall(f, Params)));
}
/// <summary>
/// Adds a job to the timer to operate asyncronously.
/// </summary>
/// <param name="Schedule">The schedule that this delegate is to be run on.</param>
/// <param name="f">The delegate to run</param>
/// <param name="Params">The method parameters to pass if you leave any DateTime parameters unbound, then they will be set with the scheduled run time of the
/// method. Any unbound object parameters will get this Job object passed in.</param>
public void AddAsyncJob(IScheduledItem Schedule, Delegate f, params object[] Params)
{
TimerJob Event = new TimerJob(Schedule, new DelegateMethodCall(f, Params));
Event.SyncronizedEvent = false;
_Jobs.Add(Event);
}
/// <summary>
/// Adds a job to the timer.
/// </summary>
/// <param name="Event"></param>
public void AddJob(TimerJob Event)
{
_Jobs.Add(Event);
}
/// <summary>
/// Clears out all scheduled jobs.
/// </summary>
public void ClearJobs()
{
_Jobs.Clear();
}
/// <summary>
/// Begins executing all assigned jobs at the scheduled times
/// </summary>
public void Start()
{
_StopFlag = false;
QueueNextTime(EventStorage.ReadLastTime());
}
/// <summary>
/// Halts executing all jobs. When the timer is restarted all jobs that would have run while the timer was stopped are re-tried.
/// </summary>
public void Stop()
{
_StopFlag = true;
_Timer.Stop();
}
/// <summary>
/// EventStorage determines the method used to store the last event fire time. It defaults to keeping it in memory.
/// </summary>
public IEventStorage EventStorage = new LocalEventStorage();
public event ExceptionEventHandler Error;
#region Private Methods and Fields
/// <summary>
/// This is here to enhance accuracy. Even if nothing is scheduled the timer sleeps for a maximum of 1 minute.
/// </summary>
private static TimeSpan MAX_INTERVAL = new TimeSpan(0, 1, 0);
private DateTime _LastTime;
private Timer _Timer;
private TimerJobList _Jobs;
private volatile bool _StopFlag;
private double NextInterval(DateTime thisTime)
{
TimeSpan interval = _Jobs.NextRunTime(thisTime)-thisTime;
if (interval > MAX_INTERVAL)
interval = MAX_INTERVAL;
//Handles the case of 0 wait time, the interval property requires a duration > 0.
return (interval.TotalMilliseconds == 0) ? 1 : interval.TotalMilliseconds;
}
private void QueueNextTime(DateTime thisTime)
{
_Timer.Interval = NextInterval(thisTime);
System.Diagnostics.Debug.WriteLine(_Timer.Interval);
_LastTime = thisTime;
EventStorage.RecordLastTime(thisTime);
_Timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
try
{
if (_Jobs == null)
return;
_Timer.Stop();
foreach(TimerJob Event in _Jobs.Jobs)
{
try { Event.Execute(this, _LastTime, e.SignalTime, Error); }
catch (Exception ex) { OnError(DateTime.Now, Event, ex); }
}
}
catch (Exception ex)
{
OnError(DateTime.Now, null, ex);
}
finally
{
if (_StopFlag == false)
QueueNextTime(e.SignalTime);
}
}
private void OnError(DateTime eventTime, TimerJob job, Exception e)
{
if (Error == null)
return;
try { Error(this, new ExceptionEventArgs(eventTime, e)); }
catch (Exception) {}
}
#endregion
#region IDisposable Members
public void Dispose()
{
if (_Timer != null)
_Timer.Dispose();
}
#endregion
}
public class ScheduleTimer : ScheduleTimerBase
{
/// <summary>
/// Add event is used in conjunction with the Elaspsed event handler. Set the Elapsed handler, add your schedule and call start.
/// </summary>
/// <param name="Schedule">The schedule to fire the event at. Adding additional schedules will cause the event to fire whenever either schedule calls for it.</param>
public void AddEvent(IScheduledItem Schedule)
{
if (Elapsed == null)
throw new ArgumentNullException("Elapsed", "member variable is null.");
AddJob(new TimerJob(Schedule, new DelegateMethodCall(Elapsed)));
}
/// <summary>
/// The event to fire when you only need to fire one event.
/// </summary>
public event ScheduledEventHandler Elapsed;
}
/// <summary>
/// ExceptionEventArgs allows exceptions to be captured and sent to the OnError event of the timer.
/// </summary>
public class ExceptionEventArgs : EventArgs
{
public ExceptionEventArgs(DateTime eventTime, Exception e)
{
EventTime = eventTime;
Error = e;
}
public DateTime EventTime;
public Exception Error;
}
/// <summary>
/// ExceptionEventHandler is the method type used by the OnError event for the timer.
/// </summary>
public delegate void ExceptionEventHandler(object sender, ExceptionEventArgs Args);
public class ScheduledEventArgs : EventArgs
{
public ScheduledEventArgs(DateTime eventTime)
{
EventTime = eventTime;
}
public DateTime EventTime;
}
public delegate void ScheduledEventHandler(object sender, ScheduledEventArgs e);
/// <summary>
/// The IResultFilter interface represents filters that either sort the events for an interval or
/// remove duplicate events either selecting the first or the last event.
/// </summary>
public interface IResultFilter
{
void FilterResultsInInterval(DateTime Start, DateTime End, ArrayList List);
}
/// <summary>
/// IEventStorage is used to provide persistance of schedule during service shutdowns.
/// </summary>
public interface IEventStorage
{
void RecordLastTime(DateTime Time);
DateTime ReadLastTime();
}
}