/*************************************************************************** * 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 { /// /// 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. /// public class ScheduleTimerBase : IDisposable { public ScheduleTimerBase() { _Timer = new Timer(); _Timer.AutoReset = false; _Timer.Elapsed += new ElapsedEventHandler(Timer_Elapsed); _Jobs = new TimerJobList(); _LastTime = DateTime.MaxValue; } /// /// Adds a job to the timer. This method passes in a delegate and the parameters similar to the Invoke method of windows forms. /// /// The schedule that this delegate is to be run on. /// The delegate to run /// 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. public void AddJob(IScheduledItem Schedule, Delegate f, params object[] Params) { _Jobs.Add(new TimerJob(Schedule, new DelegateMethodCall(f, Params))); } /// /// Adds a job to the timer to operate asyncronously. /// /// The schedule that this delegate is to be run on. /// The delegate to run /// 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. 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); } /// /// Adds a job to the timer. /// /// public void AddJob(TimerJob Event) { _Jobs.Add(Event); } /// /// Clears out all scheduled jobs. /// public void ClearJobs() { _Jobs.Clear(); } /// /// Begins executing all assigned jobs at the scheduled times /// public void Start() { _StopFlag = false; QueueNextTime(EventStorage.ReadLastTime()); } /// /// Halts executing all jobs. When the timer is restarted all jobs that would have run while the timer was stopped are re-tried. /// public void Stop() { _StopFlag = true; _Timer.Stop(); } /// /// EventStorage determines the method used to store the last event fire time. It defaults to keeping it in memory. /// public IEventStorage EventStorage = new LocalEventStorage(); public event ExceptionEventHandler Error; #region Private Methods and Fields /// /// This is here to enhance accuracy. Even if nothing is scheduled the timer sleeps for a maximum of 1 minute. /// 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 { /// /// Add event is used in conjunction with the Elaspsed event handler. Set the Elapsed handler, add your schedule and call start. /// /// The schedule to fire the event at. Adding additional schedules will cause the event to fire whenever either schedule calls for it. public void AddEvent(IScheduledItem Schedule) { if (Elapsed == null) throw new ArgumentNullException("Elapsed", "member variable is null."); AddJob(new TimerJob(Schedule, new DelegateMethodCall(Elapsed))); } /// /// The event to fire when you only need to fire one event. /// public event ScheduledEventHandler Elapsed; } /// /// ExceptionEventArgs allows exceptions to be captured and sent to the OnError event of the timer. /// public class ExceptionEventArgs : EventArgs { public ExceptionEventArgs(DateTime eventTime, Exception e) { EventTime = eventTime; Error = e; } public DateTime EventTime; public Exception Error; } /// /// ExceptionEventHandler is the method type used by the OnError event for the timer. /// 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); /// /// 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. /// public interface IResultFilter { void FilterResultsInInterval(DateTime Start, DateTime End, ArrayList List); } /// /// IEventStorage is used to provide persistance of schedule during service shutdowns. /// public interface IEventStorage { void RecordLastTime(DateTime Time); DateTime ReadLastTime(); } }