You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ta...@apache.org on 2013/04/25 00:17:49 UTC

svn commit: r1471736 - in /activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src: main/csharp/Threads/TimerEx.cs main/csharp/Threads/TimerTask.cs test/csharp/Threads/TimerExTest.cs

Author: tabish
Date: Wed Apr 24 22:17:49 2013
New Revision: 1471736

URL: http://svn.apache.org/r1471736
Log:
Adds a new TimerEx class that implements a more scalable and controllable Timer object than the basic .NET version.  Can be used to schedule multiple tasks that are run from a single Thread.  Individual tasks can be cancelled as needed and tasks can be scheduled to repeat using either fixed rate or fixed delay execution. 

Added:
    activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerEx.cs   (with props)
    activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerTask.cs   (with props)
    activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/test/csharp/Threads/TimerExTest.cs   (with props)

Added: activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerEx.cs
URL: http://svn.apache.org/viewvc/activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerEx.cs?rev=1471736&view=auto
==============================================================================
--- activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerEx.cs (added)
+++ activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerEx.cs Wed Apr 24 22:17:49 2013
@@ -0,0 +1,809 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+using System;
+using System.Threading;
+
+namespace Apache.NMS.ActiveMQ
+{
+	/// <summary>
+	/// A facility for applications to schedule tasks for future execution in a background 
+	/// thread. Tasks may be scheduled for one-time execution, or for repeated execution at 
+	/// regular intervals.  Unlike the normal .NET Timer this Timer allows for multiple tasks
+	/// to be scheduled in a single Timer object.  
+	/// 
+	/// Corresponding to each Timer object is a single background thread that is used to execute
+	/// all of the timer's tasks, sequentially. Timer tasks should complete quickly. If a timer 
+	/// task takes excessive time to complete, it "hogs" the timer's task execution thread. This
+	/// can, in turn, delay the execution of subsequent tasks, which may "bunch up" and execute 
+	/// in rapid succession when (and if) the offending task finally completes.
+	/// 
+	/// After the last live reference to a Timer object goes away and all outstanding tasks have 
+	/// completed execution, the timer's task execution thread terminates gracefully (and becomes
+	/// subject to garbage collection). However, this can take arbitrarily long to occur. By default, 
+	/// the task execution thread does not run as a Background thread, so it is capable of keeping an 
+	/// application from terminating. If a caller wants to terminate a timer's task execution thread
+	/// rapidly, the caller should invoke the timer's cancel method.
+	/// 
+	/// If the timer's task execution thread terminates unexpectedly, any further attempt to schedule
+	/// a task on the timer will result in an IllegalStateException, as if the timer's cancel method
+	/// had been invoked.
+	/// 
+	/// This class is thread-safe: multiple threads can share a single Timer object without the 
+	/// need for external synchronization.
+	/// 
+	/// This class does not offer real-time guarantees: it schedules tasks using the 
+	/// EventWaitHandle.WaitOne(TimeSpan) method.
+	/// </summary>
+	public class TimerEx
+	{
+		#region Static Id For Anonymous Timer Naming.
+
+		private static long timerId;
+
+		private static long NextId() 
+		{
+			return Interlocked.Increment(ref timerId);
+		}
+
+		#endregion
+
+		private readonly TimerImpl impl;
+
+	    // Used to finalize thread
+	    private readonly DisposeHelper disposal;
+
+	    public TimerEx(String name, bool isBackground)
+		{
+	    	if (name == null)
+			{
+	    		throw new NullReferenceException("name is null");
+	    	}
+	        this.impl = new TimerImpl(name, isBackground);
+	        this.disposal = new DisposeHelper(impl);
+		}
+
+	    public TimerEx(String name) : this(name, false)
+		{
+	    }
+
+	    public TimerEx(bool isBackground) : this("Timer-" + TimerEx.NextId().ToString(), isBackground)
+		{
+	    }
+
+	    public TimerEx() : this(false)
+		{
+	    }
+
+		/// <summary>
+		/// Terminates this timer, discarding any currently scheduled tasks. Does not interfere
+		/// with a currently executing task (if it exists). Once a timer has been terminated, 
+		/// its execution thread terminates gracefully, and no more tasks may be scheduled on it.
+		/// 
+		/// Note that calling this method from within the run method of a timer task that was 
+		/// invoked by this timer absolutely guarantees that the ongoing task execution is the 
+		/// last task execution that will ever be performed by this timer.
+		/// 
+		/// This method may be called repeatedly; the second and subsequent calls have no effect. 
+		/// </summary>
+	    public void Cancel() 
+		{
+	        this.impl.Cancel();
+	    }
+
+		/// <summary>
+		/// Removes all cancelled tasks from this timer's task queue. Calling this method has 
+		/// no effect on the behavior of the timer, but eliminates the references to the cancelled
+		/// tasks from the queue. If there are no external references to these tasks, they become 
+		/// eligible for garbage collection.
+		/// 
+		/// Most programs will have no need to call this method. It is designed for use by the 
+		/// rare application that cancels a large number of tasks. Calling this method trades 
+		/// time for space: the runtime of the method may be proportional to n + c log n, where 
+		/// n is the number of tasks in the queue and c is the number of cancelled tasks.
+		/// 
+		/// Note that it is permissible to call this method from within a a task scheduled 
+		/// on this timer. 
+		/// </summary>
+	    public int Purge() 
+		{
+	        lock(this.impl.SyncRoot)
+			{
+	            return impl.Purge();
+	        }
+	    }
+
+		public override string ToString()
+		{
+			return string.Format("[TimerEx{0}]", this.impl.Name);
+		}
+
+		#region WaitCallback Scheduling Methods
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, DateTime when) 
+		{
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        TimeSpan delay = when - DateTime.Now;
+	        DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(-1), false);
+			return task;
+	    }
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, int delay)
+		{
+ 			if(delay < 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+			DoScheduleImpl(task, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(-1), false);
+			return task;
+	    }
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, TimeSpan delay)
+		{
+ 			if(delay.CompareTo(TimeSpan.Zero) < 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+			DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(-1), false);
+			return task;
+	    }
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, int delay, int period)
+		{
+ 			if(delay < 0 || period <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        DoScheduleImpl(task, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(period), false);
+			return task;
+	    }
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, TimeSpan delay, TimeSpan period)
+		{
+ 			if(delay.CompareTo(TimeSpan.Zero) < 0 || period.CompareTo(TimeSpan.Zero) <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        DoScheduleImpl(task, delay, period, false);
+			return task;
+	    }
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, DateTime when, int period) 
+		{
+	        if (period <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+	        
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+			TimeSpan delay = when - DateTime.Now;	        
+			DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(period), false);
+			return task;
+	    }
+
+	    public TimerTask Schedule(WaitCallback callback, object arg, DateTime when, TimeSpan period) 
+		{
+	        if (period.CompareTo(TimeSpan.Zero) <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+	        
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+			TimeSpan delay = when - DateTime.Now;	        
+			DoScheduleImpl(task, delay, period, false);
+			return task;
+	    }
+
+	    public TimerTask ScheduleAtFixedRate(WaitCallback callback, object arg, int delay, int period)
+		{
+ 			if(delay < 0 || period <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        DoScheduleImpl(task, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(period), true);
+			return task;
+	    }
+
+	    public TimerTask ScheduleAtFixedRate(WaitCallback callback, object arg, TimeSpan delay, TimeSpan period)
+		{
+ 			if(delay.CompareTo(TimeSpan.Zero) < 0 || period.CompareTo(TimeSpan.Zero) <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        DoScheduleImpl(task, delay, period, true);
+			return task;
+	    }
+
+	    public TimerTask ScheduleAtFixedRate(WaitCallback callback, object arg, DateTime when, int period)
+		{
+	        if (period <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        TimeSpan delay = when - DateTime.Now;
+			DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(period), true);
+			return task;
+	    }
+
+	    public TimerTask ScheduleAtFixedRate(WaitCallback callback, object arg, DateTime when, TimeSpan period)
+		{
+	        if (period.CompareTo(TimeSpan.Zero) <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			InternalTimerTask task = new InternalTimerTask(callback, arg);
+	        TimeSpan delay = when - DateTime.Now;
+			DoScheduleImpl(task, delay, period, true);
+			return task;
+	    }
+
+		#endregion
+
+		#region TimerTask Scheduling Methods
+
+	    public void Schedule(TimerTask task, DateTime when) 
+		{
+	        TimeSpan delay = when - DateTime.Now;
+	        DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(-1), false);
+	    }
+
+	    public void Schedule(TimerTask task, int delay)
+		{
+ 			if(delay < 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			DoScheduleImpl(task, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(-1), false);
+	    }
+
+	    public void Schedule(TimerTask task, TimeSpan delay)
+		{
+ 			if(delay.CompareTo(TimeSpan.Zero) < 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+			DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(-1), false);
+	    }
+
+	    public void Schedule(TimerTask task, int delay, int period)
+		{
+ 			if(delay < 0 || period <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+	        DoScheduleImpl(task, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(period), false);
+	    }
+
+	    public void Schedule(TimerTask task, TimeSpan delay, TimeSpan period)
+		{
+ 			if(delay.CompareTo(TimeSpan.Zero) < 0 || period.CompareTo(TimeSpan.Zero) <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+	        DoScheduleImpl(task, delay, period, false);
+	    }
+
+	    public void Schedule(TimerTask task, DateTime when, int period) 
+		{
+	        if (period <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+	        
+			TimeSpan delay = when - DateTime.Now;	        
+			DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(period), false);
+	    }
+
+	    public void Schedule(TimerTask task, DateTime when, TimeSpan period) 
+		{
+	        if (period.CompareTo(TimeSpan.Zero) <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+	        
+			TimeSpan delay = when - DateTime.Now;	        
+			DoScheduleImpl(task, delay, period, false);
+	    }
+
+	    public void ScheduleAtFixedRate(TimerTask task, int delay, int period)
+		{
+ 			if(delay < 0 || period <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+	        DoScheduleImpl(task, TimeSpan.FromMilliseconds(delay), TimeSpan.FromMilliseconds(period), true);
+	    }
+
+	    public void ScheduleAtFixedRate(TimerTask task, TimeSpan delay, TimeSpan period)
+		{
+ 			if(delay.CompareTo(TimeSpan.Zero) < 0 || period.CompareTo(TimeSpan.Zero) <= 0)
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+	        DoScheduleImpl(task, delay, period, true);
+	    }
+
+	    public void ScheduleAtFixedRate(TimerTask task, DateTime when, int period)
+		{
+	        if (period <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+	        TimeSpan delay = when - DateTime.Now;
+			DoScheduleImpl(task, delay, TimeSpan.FromMilliseconds(period), true);
+	    }
+
+	    public void ScheduleAtFixedRate(TimerTask task, DateTime when, TimeSpan period)
+		{
+	        if (period.CompareTo(TimeSpan.Zero) <= 0) 
+			{
+	            throw new ArgumentOutOfRangeException();
+	        }
+
+	        TimeSpan delay = when - DateTime.Now;
+			DoScheduleImpl(task, delay, period, true);
+	    }
+
+		#endregion
+
+		#region Implementation of Scheduling method.
+
+	    private void DoScheduleImpl(TimerTask task, TimeSpan delay, TimeSpan period, bool fixedRate) 
+		{
+			if (task == null)
+			{
+				throw new ArgumentNullException("TimerTask cannot be null");
+			}
+
+			lock(this.impl.SyncRoot)
+			{
+				if (impl.Cancelled) 
+				{
+	                throw new InvalidOperationException();
+	            }
+
+	            DateTime when = DateTime.Now + delay;
+
+	            lock(task.syncRoot)
+				{
+	                if (task.IsScheduled)
+					{
+	                    throw new InvalidOperationException();
+	                }
+
+	                if (task.cancelled)
+					{
+	                    throw new InvalidOperationException("Task is already cancelled");
+	                }
+
+	                task.when = when;
+	                task.period = period;
+	                task.fixedRate = fixedRate;
+	            }
+
+	            // insert the newTask into queue
+	            impl.InsertTask(task);
+	        }
+	    }
+
+		#endregion
+
+		#region Interal TimerTask to invoking WaitCallback tasks
+
+		private class InternalTimerTask : TimerTask
+		{
+			private WaitCallback task;
+			private object taskArg;
+
+			public InternalTimerTask(WaitCallback task, object taskArg)
+			{
+				if (task == null)
+				{
+					throw new ArgumentNullException("The WaitCallback task cannot be null");
+				}
+
+				this.task = task;
+				this.taskArg = taskArg;
+			}
+
+			public override void Run()
+			{
+				this.task(taskArg);
+			}
+		}
+
+		#endregion
+
+		#region Timer Heap that sorts Tasks into timed order.
+
+        private sealed class TimerHeap  
+		{	        
+			internal static readonly int DEFAULT_HEAP_SIZE = 256;
+            
+			internal TimerTask[] timers = new TimerTask[DEFAULT_HEAP_SIZE];
+            internal int size = 0;
+            internal int deletedCancelledNumber = 0;
+
+            public TimerTask Minimum() 
+			{
+                return timers[0];
+            }
+
+            public bool IsEmpty() 
+			{
+                return size == 0;
+            }
+
+            public void Insert(TimerTask task) 
+			{
+                if (timers.Length == size) 
+				{
+                    TimerTask[] appendedTimers = new TimerTask[size * 2];
+					timers.CopyTo(appendedTimers, 0);
+                    timers = appendedTimers;
+                }
+                timers[size++] = task;
+                UpHeap();
+            }
+
+            public void Delete(int pos) 
+			{
+                // posible to delete any position of the heap
+                if (pos >= 0 && pos < size) 
+				{
+                    timers[pos] = timers[--size];
+                    timers[size] = null;
+                    DownHeap(pos);
+                }
+            }
+
+            private void UpHeap() 
+			{
+                int current = size - 1;
+                int parent = (current - 1) / 2;
+
+                while (timers[current].when < timers[parent].when) 
+				{
+                    // swap the two
+                    TimerTask tmp = timers[current];
+                    timers[current] = timers[parent];
+                    timers[parent] = tmp;
+
+                    // update pos and current
+                    current = parent;
+                    parent = (current - 1) / 2;
+                }
+            }
+
+            private void DownHeap(int pos) 
+			{
+                int current = pos;
+                int child = 2 * current + 1;
+
+                while (child < size && size > 0) 
+				{
+                    // compare the children if they exist
+                    if (child + 1 < size && timers[child + 1].when < timers[child].when) 
+					{
+                        child++;
+                    }
+
+                    // compare selected child with parent
+                    if (timers[current].when < timers[child].when) 
+					{
+                        break;
+                    }
+
+                    // swap the two
+                    TimerTask tmp = timers[current];
+                    timers[current] = timers[child];
+                    timers[child] = tmp;
+
+                    // update pos and current
+                    current = child;
+                    child = 2 * current + 1;
+                }
+            }
+
+            public void Reset() 
+			{
+                timers = new TimerTask[DEFAULT_HEAP_SIZE];
+                size = 0;
+            }
+
+            public void AdjustMinimum() 
+			{
+                DownHeap(0);
+            }
+
+            public void DeleteIfCancelled() 
+			{
+                for (int i = 0; i < size; i++) 
+				{
+                    if (timers[i].cancelled) 
+					{
+                        deletedCancelledNumber++;
+                        Delete(i);
+                        // re-try this point
+                        i--;
+                    }
+                }
+            }
+
+            internal int GetTask(TimerTask task) 
+			{
+                for (int i = 0; i < timers.Length; i++) 
+				{
+                    if (timers[i] == task) 
+					{
+                        return i;
+                    }
+                }
+                return -1;
+            }
+        }
+
+		#endregion
+
+		#region TimerEx Task Runner Implementation
+
+		private sealed class TimerImpl
+		{
+	        private bool cancelled;
+	        private bool finished;
+			private String name;
+	        private TimerHeap tasks = new TimerHeap();
+			private System.Threading.Thread runner;
+			private object syncRoot = new object();
+
+	        public TimerImpl(String name, bool isBackground) 
+			{
+				this.name = name;
+				this.runner = new Thread(new ThreadStart(this.Run));
+				this.runner.Name = name;
+				this.runner.IsBackground = isBackground;
+	            this.runner.Start();
+	        }
+
+			public String Name
+			{
+				get { return this.name; }
+			}
+
+			public object SyncRoot
+			{
+				get { return this.syncRoot; }
+			}
+
+			public bool Cancelled
+			{
+				get { return this.cancelled; }
+			}
+
+			public bool Finished 
+			{
+				set { this.finished = value; }
+			}
+
+			/// <summary>
+			/// Run this Timers event loop in its own Thread.
+			/// </summary>
+	        public void Run() 
+			{
+	            while (true) 
+				{
+	                TimerTask task;
+	                lock (this.syncRoot) 
+					{
+	                    // need to check cancelled inside the synchronized block
+	                    if (cancelled) 
+						{
+	                        return;
+	                    }
+
+	                    if (tasks.IsEmpty()) 
+						{
+	                        if (finished) 
+							{
+	                            return;
+	                        }
+	                        
+							// no tasks scheduled -- sleep until any task appear
+	                        try
+							{
+								Monitor.Wait(this.syncRoot);
+	                        } 
+							catch (ThreadInterruptedException) 
+							{
+	                        }
+	                        continue;
+	                    }
+
+	                    DateTime currentTime = DateTime.Now;
+
+	                    task = tasks.Minimum();
+	                    TimeSpan timeToSleep;
+
+	                    lock (task.syncRoot)
+						{
+	                        if (task.cancelled) 
+							{
+	                            tasks.Delete(0);
+	                            continue;
+	                        }
+
+	                        // check the time to sleep for the first task scheduled
+	                        timeToSleep = task.when - currentTime;
+	                    }
+
+	                    if (timeToSleep.CompareTo(TimeSpan.Zero) > 0) 
+						{
+	                        // sleep!
+	                        try 
+							{
+								Monitor.Wait(this.syncRoot, timeToSleep);
+	                        } 
+							catch (ThreadInterruptedException) 
+							{
+	                        }
+	                        continue;
+	                    }
+
+	                    // no sleep is necessary before launching the task
+	                    lock (task.syncRoot) 
+						{
+	                        int pos = 0;
+	                        if (tasks.Minimum().when != task.when) 
+							{
+	                            pos = tasks.GetTask(task);
+	                        }
+	                        if (task.cancelled)
+							{
+	                            tasks.Delete(tasks.GetTask(task));
+	                            continue;
+	                        }
+
+	                        // set time to schedule
+	                        task.ScheduledTime = task.when;
+
+	                        // remove task from queue
+	                        tasks.Delete(pos);
+
+	                        // set when the next task should be launched
+	                        if (task.period.CompareTo(TimeSpan.Zero) >= 0) 
+							{
+	                            // this is a repeating task,
+	                            if (task.fixedRate) 
+								{
+	                                // task is scheduled at fixed rate
+	                                task.when = task.when + task.period;
+	                            } 
+								else 
+								{
+	                                // task is scheduled at fixed delay
+	                                task.when = DateTime.Now + task.period;
+	                            }
+
+	                            // insert this task into queue
+	                            InsertTask(task);
+	                        }
+							else 
+							{
+	                            task.when = DateTime.MinValue;
+	                        }
+	                    }
+	                }
+
+	                bool taskCompletedNormally = false;
+	                try 
+					{
+	                    task.Run();
+	                    taskCompletedNormally = true;
+	                }
+					finally 
+					{
+	                    if (!taskCompletedNormally) 
+						{
+	                        lock (this) 
+							{
+	                            cancelled = true;
+	                        }
+	                    }
+	                }
+	            }
+	        }
+
+	        public void InsertTask(TimerTask newTask) 
+			{
+	            // callers are synchronized
+	            tasks.Insert(newTask);
+				Monitor.Pulse(this.syncRoot);
+	        }
+
+	        public void Cancel() 
+			{
+				lock(this.syncRoot)
+				{
+	            	cancelled = true;
+	            	tasks.Reset();
+					Monitor.Pulse(this.syncRoot);
+				}
+	        }
+
+	        public int Purge() 
+			{
+	            if (tasks.IsEmpty()) 
+				{
+	                return 0;
+	            }
+	            
+				// callers are synchronized
+	            tasks.deletedCancelledNumber = 0;
+	            tasks.DeleteIfCancelled();
+	            return tasks.deletedCancelledNumber;
+	        }
+	    }
+
+		#endregion
+
+		#region Helper class to handle Timer shutdown when Disposed
+
+		private sealed class DisposeHelper : IDisposable
+		{
+			private readonly TimerImpl impl;
+			
+			public DisposeHelper(TimerImpl impl) 
+			{
+				this.impl = impl;
+			}
+			
+			public void Dispose() 
+			{
+				lock(impl.SyncRoot)
+				{
+					impl.Finished = true;
+					Monitor.PulseAll(impl.SyncRoot);
+				}
+			}
+		}
+
+		#endregion
+	}
+}
+

Propchange: activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerEx.cs
------------------------------------------------------------------------------
    svn:eol-style = native

Added: activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerTask.cs
URL: http://svn.apache.org/viewvc/activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerTask.cs?rev=1471736&view=auto
==============================================================================
--- activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerTask.cs (added)
+++ activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerTask.cs Wed Apr 24 22:17:49 2013
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Threading;
+
+namespace Apache.NMS.ActiveMQ
+{
+	/// <summary>
+	/// A Task that is run in a Timer instance either once or repeatedly.
+	/// </summary>
+	public abstract class TimerTask
+	{
+		internal object syncRoot = new object();
+
+		internal DateTime when = DateTime.MinValue;
+		internal DateTime scheduledTime = DateTime.MinValue;
+		internal TimeSpan period;
+		internal bool cancelled;
+		internal bool fixedRate;
+
+		protected TimerTask()
+		{
+		}
+
+		public bool Cancel()
+		{
+			lock(this.syncRoot)
+			{
+	            bool willRun = !cancelled && when != DateTime.MinValue;
+	            cancelled = true;
+	            return willRun;
+	        }
+		}
+
+		public DateTime ScheduledExecutionTime
+		{
+			get
+			{
+				lock(this.syncRoot)
+				{
+					return this.scheduledTime;
+				}
+			}
+		}
+
+		public abstract void Run();
+
+		#region Timer Methods
+
+		internal DateTime When
+		{
+			get
+			{
+				lock(this.syncRoot)
+				{
+					return this.when;
+				}
+			}
+		}
+
+	    internal DateTime ScheduledTime
+		{
+			set 
+			{
+				lock(this.syncRoot)
+				{
+					this.scheduledTime = value;
+		        }
+			}
+	    }
+
+	    internal bool IsScheduled
+		{
+			get
+			{
+				lock(this.syncRoot)
+				{
+	            	return when != DateTime.MinValue || scheduledTime != DateTime.MinValue;
+	        	}
+			}
+	    }
+
+		#endregion
+	}
+}
+

Propchange: activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/main/csharp/Threads/TimerTask.cs
------------------------------------------------------------------------------
    svn:eol-style = native

Added: activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/test/csharp/Threads/TimerExTest.cs
URL: http://svn.apache.org/viewvc/activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/test/csharp/Threads/TimerExTest.cs?rev=1471736&view=auto
==============================================================================
--- activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/test/csharp/Threads/TimerExTest.cs (added)
+++ activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/test/csharp/Threads/TimerExTest.cs Wed Apr 24 22:17:49 2013
@@ -0,0 +1,1565 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System;
+using System.Threading;
+using Apache.NMS.Util;
+using Apache.NMS.ActiveMQ.Threads;
+using NUnit.Framework;
+
+namespace Apache.NMS.ActiveMQ.Test
+{
+    [TestFixture]
+	public class TimerExTest
+	{
+		TestData data;
+
+		class TestData
+		{
+			public object sync = new object();
+			public int timerCounter = 0;
+		}
+
+		class TimerTestTask : TimerTask
+		{
+			int wasRun = 0;
+
+			// Should we sleep for 200 ms each run()?
+			bool sleepInRun = false;
+
+			// Should we increment the timerCounter?
+			bool incrementCount = false;
+
+			// Should we terminate the timer at a specific timerCounter?
+			int terminateCount = -1;
+
+			// The timer we belong to
+			TimerEx timer = null;
+
+			TestData data;
+
+			public TimerTestTask(TestData data) 
+			{
+				this.data = data;
+			}
+
+			public TimerTestTask(TimerEx t, TestData data) 
+			{
+				this.timer = t;
+				this.data = data;
+			}
+
+			public override void Run() 
+			{
+				lock(this) 
+				{
+					wasRun++;
+				}
+				if (incrementCount)
+				{
+					data.timerCounter++;
+				}
+				if (terminateCount == data.timerCounter && timer != null)
+				{
+					timer.Cancel();
+				}
+				if (sleepInRun) 
+				{
+					try 
+					{
+						Thread.Sleep(200);
+					}
+					catch (ThreadInterruptedException) 
+					{
+					}
+				}
+
+				lock(data.sync)
+				{
+					Monitor.Pulse(data.sync);
+				}
+			}
+
+			public int WasRun()
+			{
+				lock(this)
+				{
+					return wasRun;
+				}
+			}
+
+			public void SleepInRun(bool sleepInRun) 
+			{
+				this.sleepInRun = sleepInRun;
+			}
+
+			public void IncrementCount(bool incrementCount) 
+			{
+				this.incrementCount = incrementCount;
+			}
+
+			public void TerminateCount(int terminateCount) 
+			{
+				this.terminateCount = terminateCount;
+			}
+		}
+
+		private void WaitCallbackTask(object arg)
+		{
+			TimerTestTask task = arg as TimerTestTask;
+			task.Run();
+		}
+
+		class SlowThenFastTask : TimerTask 
+		{
+			int wasRun = 0;
+			DateTime startedAt;
+			TimeSpan lastDelta;
+
+			public override void Run() 
+			{
+				if (wasRun == 0)
+				{
+					startedAt = DateTime.Now;
+				}
+				lastDelta = DateTime.Now - 
+					(startedAt + (TimeSpan.FromMilliseconds(100 * wasRun)));
+				wasRun++;
+				if (wasRun == 2) 
+				{
+					try 
+					{
+						Thread.Sleep(200);
+					}
+					catch (ThreadInterruptedException) 
+					{
+					}
+				}
+			}
+
+			public TimeSpan LastDelta
+			{
+				get { return lastDelta; }
+			}
+
+			public int WasRun() 
+			{
+				return wasRun;
+			}
+		}
+
+		private void Sleep(int milliseconds)
+		{
+			try 
+			{
+				Thread.Sleep(milliseconds);
+			}
+			catch (ThreadInterruptedException) 
+			{
+			}
+		}
+
+		private void Wait(int milliseconds)
+		{
+			lock (data.sync) 
+			{
+				try 
+				{
+					Monitor.Wait(data.sync, milliseconds);
+				}
+				catch (ThreadInterruptedException) 
+				{
+				}
+			}
+		}
+
+		[SetUp]
+		public void SetUp()
+		{
+			this.data = new TestData();
+		}
+
+		[Test]
+		public void TestConstructorBool() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a task is run
+				t = new TimerEx(true);
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(200));
+				Wait(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.Run() method not called after 200ms");
+				t.Cancel();
+			} 
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestConstructor() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(200));
+				Wait(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.Run() method not called after 200ms");
+				t.Cancel();
+			} 
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestConstructorStringBool() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a task is run
+				t = new TimerEx("TestConstructorStringBool", true);
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(200));
+				Wait(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.Run() method not called after 200ms");
+				t.Cancel();
+			} 
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestConstructorString() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a task is run
+				t = new TimerEx("TestConstructorString");
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(200));
+				Wait(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.Run() method not called after 200ms");
+				t.Cancel();
+			}
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+	    public void TestConstructorThrowsException() 
+		{
+	        try 
+			{
+	            new TimerEx(null, true);
+	            Assert.Fail("NullReferenceException expected");
+	        }
+			catch (NullReferenceException) 
+			{
+	            //expected
+	        }
+
+	        try 
+			{
+	            new TimerEx(null, false);
+	            Assert.Fail("NullReferenceException expected");
+	        }
+			catch (NullReferenceException) 
+			{
+	            //expected
+	        }
+	    }
+
+		[Test]
+		public void TestCancel() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a task throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(200));
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception, "Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure a task is run but not after cancel
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(500));
+				Wait(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method not called after 200ms");
+				t.Cancel();
+				Wait(500);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method should not have been called after cancel");
+
+				// Ensure you can call cancel more than once
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(500));
+				Wait(500);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method not called after 200ms");
+				t.Cancel();
+				t.Cancel();
+				t.Cancel();
+				Wait(500);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method should not have been called after cancel");
+
+				// Ensure that a call to cancel from within a timer ensures no more
+				// run
+				t = new TimerEx();
+				testTask = new TimerTestTask(t, data);
+				testTask.IncrementCount(true);
+				testTask.TerminateCount(5); // Terminate after 5 runs
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				lock (data.sync) 
+				{
+					try 
+					{
+						Monitor.Wait(data.sync, 500);
+						Monitor.Wait(data.sync, 500);
+						Monitor.Wait(data.sync, 500);
+						Monitor.Wait(data.sync, 500);
+						Monitor.Wait(data.sync, 500);
+						Monitor.Wait(data.sync, 500);
+					}
+					catch (ThreadInterruptedException) 
+					{
+					}
+				}
+				Assert.AreEqual(5, testTask.WasRun(),
+					"TimerTask.run() method should be called 5 times not " + testTask.WasRun());
+				t.Cancel();
+				Sleep(200);
+			} 
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+	    public void TestPurge()
+		{
+	        TimerEx t = null;
+	        try 
+			{
+	            t = new TimerEx();
+	            Assert.AreEqual(0, t.Purge());
+
+	            TimerTestTask[] tasks = new TimerTestTask[100];
+	            int[] delayTime = { 50, 80, 20, 70, 40, 10, 90, 30, 60 };
+
+	            int j = 0;
+	            for (int i = 0; i < 100; i++) 
+				{
+	                tasks[i] = new TimerTestTask(data);
+	                t.Schedule(tasks[i], TimeSpan.FromMilliseconds(delayTime[j++]), TimeSpan.FromMilliseconds(200));
+	                if (j == 9) 
+					{
+	                    j = 0;
+	                }
+	            }
+
+	            for (int i = 0; i < 50; i++)
+				{
+	                tasks[i].Cancel();
+	            }
+
+	            Assert.IsTrue(t.Purge() <= 50);
+	            Assert.AreEqual(0, t.Purge());
+	        } 
+			finally 
+			{
+	            if (t != null) 
+				{
+	                t.Cancel();
+	            }
+	        }
+	    }
+
+		[Test]
+		public void TestScheduleWaitCallbackDateTime() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(callback, testTask, d);
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure that the returned task from the WaitCallback schedule method
+				// cancel the task and prevent it from being run.
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(200);
+				TimerTask scheduled = t.Schedule(callback, testTask, d);
+				scheduled.Cancel();
+				Sleep(1000);
+				Assert.AreEqual(0, testTask.WasRun(), "Cancelled task shouldn't have run.");
+
+				// Ensure a TimerEx throws a ArgumentNullException if the task is null
+				t = new TimerEx();
+				exception = false;
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				try 
+				{
+					t.Schedule(null, testTask, d);
+				}
+				catch (ArgumentNullException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a null task should throw ArgumentNullException");
+				t.Cancel();
+
+				// Ensure a task is run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(200);
+				t.Schedule(callback, testTask, d);
+				Sleep(400);
+
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method not called after 200ms");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Schedule(callback, testTask, d);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(150);
+				t.Schedule(callback, testTask, d);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(70);
+				t.Schedule(callback, testTask, d);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(10);
+				t.Schedule(callback, testTask, d);
+				Sleep(400);
+
+				Assert.AreEqual(4, data.timerCounter,
+					"Multiple tasks should have incremented counter 4 times not " + data.timerCounter);
+				t.Cancel();
+			}
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleWaitCallbackWithDelay()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				t.Schedule(callback, testTask, 200);
+				Sleep(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method not called after 200ms");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleWaitCallbackWithDelayAsTimeSpan()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				t.Schedule(callback, testTask, TimeSpan.FromMilliseconds(200));
+				Sleep(1000);
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method not called after 200ms");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleWaitCallbackWithDelayAndPeriod()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				t.Schedule(callback, testTask, 200, 100);
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleWaitCallbackWithDelayAndPeriodTimeSpan()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				t.Schedule(callback, testTask, TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(100));
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleWaitCallbackWithDateTimePeriod()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Schedule(callback, testTask, d, 100);
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleWaitCallbackWithDateTimePeriodTimeSpan()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Schedule(callback, testTask, d, TimeSpan.FromMilliseconds(100));
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleAtFixedRateWaitCallbackWithDelayPeriod()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				t.ScheduleAtFixedRate(callback, testTask, 200, 100);
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleAtFixedRateWaitCallbackWithDelayPeriodTimeSpan()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				t.ScheduleAtFixedRate(
+					callback, testTask, TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(100));
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleAtFixedRateWaitCallbackWithDateTimePeriod()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.ScheduleAtFixedRate(callback, testTask, d, 100);
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleAtFixedRateWaitCallbackWithDateTimePeriodTimeSpan()
+		{
+			TimerEx t = null;
+			try
+			{
+				// Ensure a task is run
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				WaitCallback callback = new WaitCallback(WaitCallbackTask);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.ScheduleAtFixedRate(callback, testTask, d, TimeSpan.FromMilliseconds(100));
+				Sleep(1000);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+				    "TimerTask.run() method not called at least twice after 1 second sleep");
+				t.Cancel();
+			}
+			finally
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleTimerTaskDateTime() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(testTask, d);
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an InvalidOperationException if task already cancelled
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				testTask.Cancel();
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, d);
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task after cancelling it should throw exception");
+				t.Cancel();
+
+				// Ensure a TimerEx throws a ArgumentNullException if the task is null
+				t = new TimerEx();
+				exception = false;
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				try 
+				{
+					t.Schedule(null, d);
+				}
+				catch (ArgumentNullException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a null task should throw ArgumentNullException");
+				t.Cancel();
+
+				// Ensure a task is run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(200);
+				t.Schedule(testTask, d);
+				Sleep(400);
+
+				Assert.AreEqual(1, testTask.WasRun(), "TimerTask.run() method not called after 200ms");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Schedule(testTask, d);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(150);
+				t.Schedule(testTask, d);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(70);
+				t.Schedule(testTask, d);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(10);
+				t.Schedule(testTask, d);
+				Sleep(400);
+
+				Assert.AreEqual(4, data.timerCounter,
+					"Multiple tasks should have incremented counter 4 times not " + data.timerCounter);
+				t.Cancel();
+			}
+			finally 
+			{
+				if (t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleTimerTaskDelay() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(testTask, 100);
+				} 
+				catch (InvalidOperationException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an InvalidOperationException if task already
+				// cancelled
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.Cancel();
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, 100);
+				} 
+				catch (InvalidOperationException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task after cancelling it should throw exception");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if delay is
+				// negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, -100);
+				}
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task with negative delay should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws a ArgumentNullException if the task is null
+				t = new TimerEx();
+				exception = false;
+				try
+				{
+					t.Schedule(null, 10);
+				} 
+				catch (ArgumentNullException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a null task should throw ArgumentNullException");
+				t.Cancel();
+
+				// Ensure proper sequence of exceptions
+				t = new TimerEx();
+				exception = false;
+				try 
+				{
+					t.Schedule(null, -10);
+				}
+				catch (ArgumentNullException)
+				{
+				}
+				catch (ArgumentOutOfRangeException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception, 
+					"Scheduling a null task with negative delays should throw IllegalArgumentException first");
+				t.Cancel();
+
+				// Ensure a task is run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				t.Schedule(testTask, 200);
+				Sleep(400);
+				Assert.AreEqual(1, testTask.WasRun(),
+					"TimerTask.run() method not called after 200ms");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, 100);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, 150);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, 70);
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, 10);
+				Sleep(400);
+				Assert.AreEqual(4, data.timerCounter,
+					"Multiple tasks should have incremented counter 4 times not " + data.timerCounter);
+				t.Cancel();
+			}
+			finally 
+			{
+				if(t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleTimerTaskDelayTimeSpan() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100));
+				} 
+				catch (InvalidOperationException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an InvalidOperationException if task already
+				// cancelled
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.Cancel();
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100));
+				} 
+				catch (InvalidOperationException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task after cancelling it should throw exception");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if delay is
+				// negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(-100));
+				}
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task with negative delay should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws a ArgumentNullException if the task is null
+				t = new TimerEx();
+				exception = false;
+				try
+				{
+					t.Schedule(null, TimeSpan.FromMilliseconds(10));
+				} 
+				catch (ArgumentNullException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a null task should throw ArgumentNullException");
+				t.Cancel();
+
+				// Ensure proper sequence of exceptions
+				t = new TimerEx();
+				exception = false;
+				try 
+				{
+					t.Schedule(null, TimeSpan.FromMilliseconds(-10));
+				}
+				catch (ArgumentNullException)
+				{
+				}
+				catch (ArgumentOutOfRangeException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception, 
+					"Scheduling a null task with negative delays should throw IllegalArgumentException first");
+				t.Cancel();
+
+				// Ensure a task is run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(200));
+				Sleep(400);
+				Assert.AreEqual(1, testTask.WasRun(),
+					"TimerTask.run() method not called after 200ms");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100));
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(150));
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(70));
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(10));
+				Sleep(400);
+				Assert.AreEqual(4, data.timerCounter,
+					"Multiple tasks should have incremented counter 4 times not " + data.timerCounter);
+				t.Cancel();
+			}
+			finally 
+			{
+				if(t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleTimerTaskDelayPeriod() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an InvalidOperationException if task already
+				// cancelled
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.Cancel();
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+					"Scheduling a task after cancelling it should throw exception");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if delay is  negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(-100), TimeSpan.FromMilliseconds(100));
+				}
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task with negative delay should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if period is negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(-100));
+				}
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task with negative period should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if period is
+				// zero
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(0));
+				} 
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task with 0 period should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws a ArgumentNullException if the task is null
+				t = new TimerEx();
+				exception = false;
+				try 
+				{
+					t.Schedule(null, TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(10));
+				} 
+				catch (ArgumentNullException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a null task should throw ArgumentNullException");
+				t.Cancel();
+
+				// Ensure proper sequence of exceptions
+				t = new TimerEx();
+				exception = false;
+				try
+				{
+					t.Schedule(null, TimeSpan.FromMilliseconds(-10), TimeSpan.FromMilliseconds(-10));
+				} 
+				catch (ArgumentNullException) 
+				{
+				} 
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a null task with negative delays should throw ArgumentOutOfRangeException first");
+				t.Cancel();
+
+				// Ensure a task is run at least twice
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				Sleep(400);
+				Assert.IsTrue(testTask.WasRun() >= 2,
+					"TimerTask.run() method should have been called at least twice (" + testTask.WasRun() + ")");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100)); // at least 9 times
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(200), TimeSpan.FromMilliseconds(100)); // at least 7 times
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(300), TimeSpan.FromMilliseconds(200)); // at least 4 times
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				t.Schedule(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(200)); // at least 4 times
+				Sleep(1200);
+				Assert.IsTrue(data.timerCounter >= 24,
+					"Multiple tasks should have incremented counter 24 times not " + data.timerCounter);
+				t.Cancel();
+			}
+			finally 
+			{
+				if(t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleTimerTaskDateTimePeriod() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Cancel();
+				bool exception = false;
+				try 
+				{
+					t.Schedule(testTask, d, TimeSpan.FromMilliseconds(100));
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an InvalidOperationException if task already cancelled
+				t = new TimerEx();
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				testTask = new TimerTestTask(data);
+				testTask.Cancel();
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, d, TimeSpan.FromMilliseconds(100));
+				} 
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task after cancelling it should throw exception");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if period is
+				// negative
+				t = new TimerEx();
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.Schedule(testTask, d, TimeSpan.FromMilliseconds(-100));
+				}
+				catch (ArgumentOutOfRangeException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a task with negative period should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws a ArgumentNullException if the task is null
+				t = new TimerEx();
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				exception = false;
+				try 
+				{
+					t.Schedule(null, d, TimeSpan.FromMilliseconds(10));
+				}
+				catch (ArgumentNullException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"Scheduling a null task should throw ArgumentNullException");
+				t.Cancel();
+
+				// Ensure a task is run at least twice
+				t = new TimerEx();
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				testTask = new TimerTestTask(data);
+				t.Schedule(testTask, d, TimeSpan.FromMilliseconds(100));
+				Sleep(800);
+				Assert.IsTrue( testTask.WasRun() >= 2,
+					"TimerTask.Run() method should have been called at least twice (" + testTask.WasRun() + ")");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.Schedule(testTask, d, TimeSpan.FromMilliseconds(100)); // at least 9 times
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(200);
+				t.Schedule(testTask, d, TimeSpan.FromMilliseconds(100)); // at least 7 times
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(300);
+				t.Schedule(testTask, d, TimeSpan.FromMilliseconds(200)); // at least 4 times
+				testTask = new TimerTestTask(data);
+				testTask.IncrementCount(true);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(400);
+				t.Schedule(testTask, d, TimeSpan.FromMilliseconds(200)); // at least 4 times
+				Sleep(3000);
+				Assert.IsTrue(data.timerCounter >= 24,
+					"Multiple tasks should have incremented counter 24 times not " + data.timerCounter);
+				t.Cancel();
+			}
+			finally 
+			{
+				if(t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleAtFixedRateTimerTaskDelayPeriod()
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Cancel();
+				bool exception = false;
+				try
+				{
+					t.ScheduleAtFixedRate(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				}
+				catch (InvalidOperationException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"scheduleAtFixedRate after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if delay is
+				// negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.ScheduleAtFixedRate(testTask, TimeSpan.FromMilliseconds(-100), TimeSpan.FromMilliseconds(100));
+				}
+				catch (ArgumentOutOfRangeException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"scheduleAtFixedRate with negative delay should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a TimerEx throws an ArgumentOutOfRangeException if period is
+				// negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.ScheduleAtFixedRate(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(-100));
+				} 
+				catch (ArgumentOutOfRangeException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception, 
+						"scheduleAtFixedRate with negative period should throw IllegalArgumentException");
+				t.Cancel();
+
+				// Ensure a task is run at least twice
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				t.ScheduleAtFixedRate(testTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				Sleep(400);
+				Assert.IsTrue(testTask.WasRun() >= 2, 
+					"TimerTask.run() method should have been called at least twice (" + testTask.WasRun() + ")");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				SlowThenFastTask slowThenFastTask = new SlowThenFastTask();
+
+				// at least 9 times even when asleep
+				t.ScheduleAtFixedRate(slowThenFastTask, TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(100));
+				Sleep(1000);
+				long lastDelta = (long) slowThenFastTask.LastDelta.TotalMilliseconds;
+				Assert.IsTrue(lastDelta < 300,
+					"Fixed Rate Schedule should catch up, but is off by " + lastDelta + " ms");
+				t.Cancel();
+			}
+			finally 
+			{
+				if(t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+
+		[Test]
+		public void TestScheduleAtFixedRateTimerTaskDateTimePeriod() 
+		{
+			TimerEx t = null;
+			try 
+			{
+				// Ensure a TimerEx throws an InvalidOperationException after cancelled
+				t = new TimerEx();
+				TimerTestTask testTask = new TimerTestTask(data);
+				t.Cancel();
+				bool exception = false;
+				DateTime d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				try 
+				{
+					t.ScheduleAtFixedRate(testTask, d, TimeSpan.FromMilliseconds(100));
+				}
+				catch (InvalidOperationException) 
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"scheduleAtFixedRate after Timer.Cancel() should throw exception");
+
+				// Ensure a TimerEx throws an IllegalArgumentException if period is
+				// negative
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				exception = false;
+				try 
+				{
+					t.ScheduleAtFixedRate(testTask, d, TimeSpan.FromMilliseconds(-100));
+				} 
+				catch (ArgumentOutOfRangeException)
+				{
+					exception = true;
+				}
+				Assert.IsTrue(exception,
+						"scheduleAtFixedRate with negative period should throw ArgumentOutOfRangeException");
+				t.Cancel();
+
+				// Ensure a task is run at least twice
+				t = new TimerEx();
+				testTask = new TimerTestTask(data);
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+				t.ScheduleAtFixedRate(testTask, d, TimeSpan.FromMilliseconds(100));
+				Sleep(400);
+				Assert.IsTrue(testTask.WasRun() >= 2,
+					"TimerTask.run() method should have been called at least twice (" + testTask.WasRun() + ")");
+				t.Cancel();
+
+				// Ensure multiple tasks are run
+				t = new TimerEx();
+				SlowThenFastTask slowThenFastTask = new SlowThenFastTask();
+				d = DateTime.Now + TimeSpan.FromMilliseconds(100);
+
+				// at least 9 times even when asleep
+				t.ScheduleAtFixedRate(slowThenFastTask, d, TimeSpan.FromMilliseconds(100));
+				Sleep(1000);
+				long lastDelta = (long) slowThenFastTask.LastDelta.TotalMilliseconds;
+				Assert.IsTrue(lastDelta < 300, 
+					"Fixed Rate Schedule should catch up, but is off by " + lastDelta + " ms");
+				t.Cancel();
+			}
+			finally 
+			{
+				if(t != null)
+				{
+					t.Cancel();
+				}
+			}
+		}
+	}
+}
+

Propchange: activemq/activemq-dotnet/Apache.NMS.ActiveMQ/trunk/src/test/csharp/Threads/TimerExTest.cs
------------------------------------------------------------------------------
    svn:eol-style = native