diff --git a/Source/Scheduling/GenericTimeSource.cs b/Source/Scheduling/GenericTimeSource.cs
index 5ab5998..47acab5 100644
--- a/Source/Scheduling/GenericTimeSource.cs
+++ b/Source/Scheduling/GenericTimeSource.cs
@@ -87,12 +87,18 @@ namespace Nuclex.Support.Scheduling {
/// True if the WaitHandle was signalled, false if the timeout was reached
///
public virtual bool WaitOne(AutoResetEvent waitHandle, long ticks) {
- checkForTimeAdjustment();
// Force a timeout at least each second so the caller can check the system time
// since we're not able to provide the DateTimeAdjusted notification
int milliseconds = (int)(ticks / TicksPerMillisecond);
- return waitHandle.WaitOne(Math.Min(1000, milliseconds), false);
+ bool signalled = waitHandle.WaitOne(Math.Min(1000, milliseconds), false);
+
+ // See whether the system date/time have been adjusted while we were asleep.
+ checkForTimeAdjustment();
+
+ // Now tell the caller whether his even was signalled
+ return signalled;
+
}
/// Current system time in UTC format
diff --git a/Source/Scheduling/Scheduler.Test.cs b/Source/Scheduling/Scheduler.Test.cs
index 0a6d9f2..1cdf595 100644
--- a/Source/Scheduling/Scheduler.Test.cs
+++ b/Source/Scheduling/Scheduler.Test.cs
@@ -29,14 +29,175 @@ using Microsoft.Win32;
using NUnit.Framework;
-#if false
-
namespace Nuclex.Support.Scheduling {
/// Unit Test for the scheduler
[TestFixture]
public class SchedulerTest {
+ #region class MockTimeSource
+
+ /// Mocked time source
+ private class MockTimeSource : ITimeSource {
+
+ /// Called when the system date/time are adjusted
+ public event EventHandler DateTimeAdjusted;
+
+ /// Initializes a new mocked time source
+ /// Start time in UTC format
+ public MockTimeSource(DateTime utcStartTime) {
+ this.currentTime = utcStartTime;
+ }
+
+ /// Waits for an AutoResetEvent to become signalled
+ /// WaitHandle the method will wait for
+ /// Number of ticks to wait
+ ///
+ /// True if the WaitHandle was signalled, false if the timeout was reached
+ /// or the time source thinks its time to recheck the system date/time.
+ ///
+ public bool WaitOne(AutoResetEvent waitHandle, long ticks) {
+ long currentTicks;
+ long eventDueTicks;
+ lock(this) {
+ this.autoResetEvent = waitHandle;
+ this.eventDueTicks += ticks;
+
+ currentTicks = this.currentTicks;
+ eventDueTicks = this.eventDueTicks;
+ }
+
+ // If we need to wait, use the wait handle. We do not use the wait handle's
+ // return value (or even its timeout) because we might trigger it ourselves
+ // to simulate the passing of time.
+ if(eventDueTicks > 0) {
+ this.autoResetEvent = waitHandle;
+ waitHandle.WaitOne();
+ this.autoResetEvent = null;
+ }
+
+ // Do not use the cached values here -- we might have used the WaitHandle and
+ // the simulation time could have been advanced while we were waiting.
+ lock(this) {
+ return (this.eventDueTicks > 0); // True = signalled, false = timed out
+ }
+ }
+
+ /// Current system time in UTC format
+ public DateTime CurrentUtcTime {
+ get { lock(this) { return this.currentTime; } }
+ }
+
+ /// How long the time source has been running
+ public long Ticks {
+ get {
+ lock(this) {
+ this.eventDueTicks = 0;
+ return this.currentTicks;
+ }
+ }
+ }
+
+ /// Advances the time of the time source
+ ///
+ /// Time span by which to advance the time source's time
+ ///
+ public void AdvanceTime(TimeSpan timeSpan) {
+ lock(this) {
+ this.currentTicks += timeSpan.Ticks;
+ this.currentTime += timeSpan;
+
+ // Problem: The Scheduler has just calculated the remaining ticks until
+ // notification occurs. Next, another thread advances simulation time
+ // and then the scheduler calls this. It will wait, even though
+ // the simultion time has progressed.
+ // To compensate this, we track the remaining time until the event is due
+ // and allow for a negative time budget if AdvanceTime() is called after
+ // the scheduler has just queried the current tick count.
+ this.eventDueTicks -= timeSpan.Ticks;
+
+ if(this.eventDueTicks <= 0) {
+ AutoResetEvent copy = this.autoResetEvent;
+ if(copy != null) {
+ copy.Set();
+ }
+ }
+ }
+ }
+
+ /// Manually triggers the date time adjusted event
+ /// New simulation time to jump to
+ public void AdjustTime(DateTime newUtcTime) {
+ lock(this) {
+ this.currentTime = newUtcTime;
+ }
+ EventHandler copy = DateTimeAdjusted;
+ if(copy != null) {
+ copy(this, EventArgs.Empty);
+ }
+ }
+
+ /// Auto reset event the time source is currently waiting on
+ private volatile AutoResetEvent autoResetEvent;
+ /// Ticks at which the auto reset event will be due
+ private long eventDueTicks;
+ /// Current time source tick counter
+ private long currentTicks;
+ /// Current system time and date
+ private DateTime currentTime;
+
+ }
+
+ #endregion // class MockTimeSource
+
+ #region class TestSubscriber
+
+ /// Subscriber used to test the scheduler notifications
+ private class TestSubscriber : IDisposable {
+
+ /// Initializes a new test subscriber
+ public TestSubscriber() {
+ this.waitHandle = new AutoResetEvent(false);
+ }
+
+ /// Immediately releases all resources owned by the instance
+ public void Dispose() {
+ if(this.waitHandle != null) {
+ this.waitHandle.Close();
+ this.waitHandle = null;
+ }
+ }
+
+ /// Callback method that can be subscribed to the scheduler
+ /// Not used
+ public void Callback(object state) {
+ Interlocked.Increment(ref this.callbackCount);
+ this.waitHandle.Set();
+ }
+
+ /// Blocks ther caller until the callback is invoked
+ ///
+ /// Maximum number of milliseconds to wait for the callback
+ ///
+ /// True if the callback was invoked, false if the call timed out
+ public bool WaitForCallback(int milliseconds) {
+ return this.waitHandle.WaitOne(milliseconds);
+ }
+
+ /// Number of times the callback has been invoked
+ public int CallbackCount {
+ get { return Thread.VolatileRead(ref this.callbackCount); }
+ }
+
+ /// Callback invocation count
+ private int callbackCount;
+ /// WaitHandle used to wait for the callback
+ private AutoResetEvent waitHandle;
+
+ }
+
+ #endregion // class TestSubscriber
+
///
/// Test whether the Scheduler can explicitely create a windows time source
///
@@ -96,10 +257,200 @@ namespace Nuclex.Support.Scheduling {
using(Scheduler scheduler = new Scheduler()) { }
}
+ ///
+ /// Verifies that the default constructor of the scheduler is working
+ ///
+ [Test]
+ public void TestThrowOnNotifyAtWithUnspecifiedDateTimeKind() {
+ using(TestSubscriber subscriber = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler()) {
+ Assert.Throws(
+ delegate() {
+ scheduler.NotifyAt(new DateTime(2000, 1, 1), subscriber.Callback);
+ }
+ );
+ }
+ }
+ }
+
+ ///
+ /// Tests whether the NotifyAt() method invokes the callback at the right time
+ ///
+ [Test]
+ public void TestNotifyAt() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ scheduler.NotifyAt(makeUtc(new DateTime(2010, 1, 2)), subscriber.Callback);
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromDays(1));
+
+ Assert.IsTrue(subscriber.WaitForCallback(1000));
+ }
+ }
+ }
+
+ ///
+ /// Verifies that a notification at an absolute time is processed correctly
+ /// if a time synchronization occurs during the wait.
+ ///
+ [Test]
+ public void TestNotifyAtWithDateTimeAdjustment() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ scheduler.NotifyAt(makeUtc(new DateTime(2010, 1, 2)), subscriber.Callback);
+
+ // Let 12 hours pass, after that, we simulate a time synchronization
+ // that puts the system 12 hours ahead of the original time.
+ mockTimeSource.AdvanceTime(TimeSpan.FromHours(12));
+ mockTimeSource.AdjustTime(new DateTime(2010, 1, 2));
+
+ Assert.IsTrue(subscriber.WaitForCallback(1000));
+ }
+ }
+ }
+
+ /// Tests whether the scheduler's Cancel() method is working
+ [Test]
+ public void TestCancelNotification() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber1 = new TestSubscriber()) {
+ using(TestSubscriber subscriber2 = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ object handle = scheduler.NotifyIn(
+ TimeSpan.FromHours(24), subscriber1.Callback
+ );
+ scheduler.NotifyIn(TimeSpan.FromHours(36), subscriber2.Callback);
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromHours(12));
+ scheduler.Cancel(handle);
+ mockTimeSource.AdvanceTime(TimeSpan.FromHours(24));
+
+ // Wait for the second subscriber to be notified. This is still a race
+ // condition (there's no guarantee the thread pool will run tasks in
+ // the order they were added), but it's the best we can do without
+ // relying on an ugly Thread.Sleep() in this test.
+ Assert.IsTrue(subscriber2.WaitForCallback(1000));
+ Assert.AreEqual(0, subscriber1.CallbackCount);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Tests the scheduler with two notifications that are scheduled in inverse
+ /// order of their due time.
+ ///
+ [Test]
+ public void TestInverseOrderNotification() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber1 = new TestSubscriber()) {
+ using(TestSubscriber subscriber2 = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ scheduler.NotifyIn(TimeSpan.FromHours(24), subscriber1.Callback);
+ scheduler.NotifyIn(TimeSpan.FromHours(12), subscriber2.Callback);
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromHours(18));
+
+ Assert.IsTrue(subscriber2.WaitForCallback(1000));
+ Assert.AreEqual(0, subscriber1.CallbackCount);
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromHours(18));
+
+ Assert.IsTrue(subscriber1.WaitForCallback(1000));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Tests the scheduler with two notifications that are scheduled to
+ /// occur at the exact same time
+ ///
+ [Test]
+ public void TestTwoNotificationsAtSameTime() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber1 = new TestSubscriber()) {
+ using(TestSubscriber subscriber2 = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ scheduler.NotifyIn(60000, subscriber1.Callback);
+ scheduler.NotifyIn(60000, subscriber2.Callback);
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromMilliseconds(60000));
+
+ Assert.IsTrue(subscriber1.WaitForCallback(1000));
+ Assert.IsTrue(subscriber2.WaitForCallback(1000));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Verifies that the scheduler's NotifyEach() method is working correctly
+ ///
+ [Test]
+ public void TestNotifyEachWithMilliseconds() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ scheduler.NotifyEach(1000, 1000, subscriber.Callback);
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromMilliseconds(4000));
+
+ // Wait for 4 invocations of the callback. We might not catch each trigger
+ // of the AutoResetEvent, but we know that it will be 4 at most.
+ for(int invocation = 0; invocation < 4; ++invocation) {
+ Assert.IsTrue(subscriber.WaitForCallback(1000));
+
+ if(subscriber.CallbackCount == 4) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Verifies that the scheduler's NotifyEach() method is working correctly
+ ///
+ [Test]
+ public void TestNotifyEachWithTimespan() {
+ MockTimeSource mockTimeSource = new MockTimeSource(new DateTime(2010, 1, 1));
+ using(TestSubscriber subscriber = new TestSubscriber()) {
+ using(Scheduler scheduler = new Scheduler(mockTimeSource)) {
+ scheduler.NotifyEach(
+ TimeSpan.FromHours(12), TimeSpan.FromHours(1), subscriber.Callback
+ );
+
+ mockTimeSource.AdvanceTime(TimeSpan.FromHours(14));
+
+ // Wait for 3 invocations of the callback. We might not catch each trigger
+ // of the AutoResetEvent, but we know that it will be 3 at most.
+ for(int invocation = 0; invocation < 3; ++invocation) {
+ Assert.IsTrue(subscriber.WaitForCallback(1000));
+
+ if(subscriber.CallbackCount == 3) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /// Returns the provided date/time value as a utc time value
+ /// Date/time value that will be returned as UTC
+ /// The provided date/time value as UTC
+ ///
+ /// This doesn't convert the time, it just returns the exact same date and time
+ /// but tags it as UTC by setting the DateTimeKind to UTC.
+ ///
+ private static DateTime makeUtc(DateTime dateTime) {
+ return new DateTime(dateTime.Ticks, DateTimeKind.Utc);
+ }
+
}
} // namespace Nuclex.Support.Scheduling
-#endif
-
#endif // UNITTEST
diff --git a/Source/Scheduling/Scheduler.cs b/Source/Scheduling/Scheduler.cs
index 0363553..be4d038 100644
--- a/Source/Scheduling/Scheduler.cs
+++ b/Source/Scheduling/Scheduler.cs
@@ -25,8 +25,6 @@ using System.Diagnostics;
using Nuclex.Support.Collections;
-#if false
-
namespace Nuclex.Support.Scheduling {
/// Schedules actions for execution at a future point in time
@@ -35,7 +33,7 @@ namespace Nuclex.Support.Scheduling {
/// One tick is 100 ns, meaning 10000 ticks equal 1 ms
private const long TicksPerMillisecond = 10000;
-#region class TimeSourceSingleton
+ #region class TimeSourceSingleton
///
/// Manages the singleton instance of the scheduler's default time source
@@ -55,7 +53,7 @@ namespace Nuclex.Support.Scheduling {
#endregion // class TimeSourceSingleton
-#region class Notification
+ #region class Notification
/// Scheduled notification
private class Notification {
@@ -77,7 +75,7 @@ namespace Nuclex.Support.Scheduling {
long intervalTicks,
long nextDueTicks,
DateTime absoluteUtcTime,
- Delegate callback
+ WaitCallback callback
) {
this.IntervalTicks = intervalTicks;
this.NextDueTicks = nextDueTicks;
@@ -100,7 +98,7 @@ namespace Nuclex.Support.Scheduling {
///
public DateTime AbsoluteUtcTime;
/// Callback that will be invoked when the notification is due
- public Delegate Callback;
+ public WaitCallback Callback;
/// Whether the notification has been cancelled
public bool Cancelled;
@@ -108,14 +106,44 @@ namespace Nuclex.Support.Scheduling {
#endregion // class Notification
+ #region class NotificationComparer
+
+ /// Compares two notifications to each other
+ private class NotificationComparer : IComparer {
+
+ /// The default instance of the notification comparer
+ public static readonly NotificationComparer Default = new NotificationComparer();
+
+ /// Compares two notifications to each other based on their time
+ /// Notification that will be compared on the left side
+ /// Notification that will be comapred on the right side
+ /// The relation of the two notification's times to each other
+ public int Compare(Notification left, Notification right) {
+ if(left.NextDueTicks > right.NextDueTicks) {
+ return -1;
+ } else if(left.NextDueTicks < right.NextDueTicks) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ #endregion // class NotificationComparer
+
+ }
+
/// Initializes a new scheduler using the default time source
public Scheduler() : this(DefaultTimeSource) { }
/// Initializes a new scheduler using the specified time source
/// Source source the scheduler will use
public Scheduler(ITimeSource timeSource) {
+ this.dateTimeAdjustedDelegate = new EventHandler(dateTimeAdjusted);
+
this.timeSource = timeSource;
- this.notifications = new PriorityQueue();
+ this.timeSource.DateTimeAdjusted += this.dateTimeAdjustedDelegate;
+
+ this.notifications = new PriorityQueue(NotificationComparer.Default);
this.notificationWaitEvent = new AutoResetEvent(false);
this.timerThread = new Thread(new ThreadStart(runTimerThread));
@@ -138,6 +166,13 @@ namespace Nuclex.Support.Scheduling {
this.timerThread.Join(2500), "Scheduler timer thread did not exit in time"
);
+ // Unsubscribe from the time source to avoid surprise events during or
+ // after shutdown
+ if(this.timeSource != null) {
+ this.timeSource.DateTimeAdjusted -= this.dateTimeAdjustedDelegate;
+ this.timeSource = null;
+ }
+
// Get rid of the notification wait event now that we've made sure that
// the timer thread is down.
this.notificationWaitEvent.Close();
@@ -145,7 +180,6 @@ namespace Nuclex.Support.Scheduling {
// Help the GC a bit
this.notificationWaitEvent = null;
this.notifications = null;
- this.timeSource = null;
// Set to null so we don't attempt to end the thread again if Dispose() is
// called multiple times.
@@ -168,7 +202,7 @@ namespace Nuclex.Support.Scheduling {
/// the notification. So if you need to be notified after a fixed time, use
/// the NotifyIn() method instead.
///
- public object NotifyAt(DateTime notificationTime, Delegate callback) {
+ public object NotifyAt(DateTime notificationTime, WaitCallback callback) {
if(notificationTime.Kind == DateTimeKind.Unspecified) {
throw new ArgumentException(
"Notification time is neither UTC or local", "notificationTime"
@@ -176,7 +210,9 @@ namespace Nuclex.Support.Scheduling {
}
DateTime notificationTimeUtc = notificationTime.ToUniversalTime();
- long remainingTicks = notificationTimeUtc.Ticks - DateTime.UtcNow.Ticks;
+ DateTime now = this.timeSource.CurrentUtcTime;
+
+ long remainingTicks = notificationTimeUtc.Ticks - now.Ticks;
long nextDueTicks = this.timeSource.Ticks + remainingTicks;
return scheduleNotification(
@@ -195,7 +231,7 @@ namespace Nuclex.Support.Scheduling {
/// Callback that will be invoked when the notification is due
///
/// A handle that can be used to cancel the notification
- public object NotifyIn(TimeSpan delay, Delegate callback) {
+ public object NotifyIn(TimeSpan delay, WaitCallback callback) {
return scheduleNotification(
new Notification(
0,
@@ -216,7 +252,7 @@ namespace Nuclex.Support.Scheduling {
/// Callback that will be invoked when the notification is due
///
/// A handle that can be used to cancel the notification
- public object NotifyIn(int delayMilliseconds, Delegate callback) {
+ public object NotifyIn(int delayMilliseconds, WaitCallback callback) {
return scheduleNotification(
new Notification(
0,
@@ -236,7 +272,7 @@ namespace Nuclex.Support.Scheduling {
/// Callback that will be invoked when the notification is due
///
/// A handle that can be used to cancel the notification
- public object NotifyEach(TimeSpan delay, TimeSpan interval, Delegate callback) {
+ public object NotifyEach(TimeSpan delay, TimeSpan interval, WaitCallback callback) {
return scheduleNotification(
new Notification(
interval.Ticks,
@@ -261,7 +297,7 @@ namespace Nuclex.Support.Scheduling {
///
/// A handle that can be used to cancel the notification
public object NotifyEach(
- int delayMilliseconds, int intervalMilliseconds, Delegate callback
+ int delayMilliseconds, int intervalMilliseconds, WaitCallback callback
) {
return scheduleNotification(
new Notification(
@@ -273,6 +309,17 @@ namespace Nuclex.Support.Scheduling {
);
}
+ /// Cancels a scheduled notification
+ ///
+ /// Handle of the notification that will be cancelled
+ ///
+ public void Cancel(object notificationHandle) {
+ Notification notification = notificationHandle as Notification;
+ if(notification != null) {
+ notification.Cancelled = true;
+ }
+ }
+
/// Returns the default time source for the scheduler
public static ITimeSource DefaultTimeSource {
get { return TimeSourceSingleton.Instance; }
@@ -297,11 +344,54 @@ namespace Nuclex.Support.Scheduling {
return CreateTimeSource(WindowsTimeSource.Available);
}
+ /// Called when the system date/time have been adjusted
+ /// Time source which detected the adjustment
+ /// Not used
+ private void dateTimeAdjusted(object sender, EventArgs arguments) {
+ lock(this.timerThread) {
+ long currentTicks = this.timeSource.Ticks;
+ DateTime currentTime = this.timeSource.CurrentUtcTime;
+
+ PriorityQueue updatedQueue = new PriorityQueue(
+ NotificationComparer.Default
+ );
+
+ // Copy all notifications from the original queue to a new one, adjusting
+ // those with an absolute notification time along the way to a new due tick
+ while(this.notifications.Count > 0) {
+ Notification notification = this.notifications.Dequeue();
+ if(!notification.Cancelled) {
+
+ // If this notification has an absolute due time, adjust its due tick
+ if(notification.AbsoluteUtcTime != DateTime.MinValue) {
+
+ // Combining recurrent notifications with absolute time isn't allowed
+ Debug.Assert(notification.IntervalTicks == 0);
+
+ // Make the adjustment
+ long remainingTicks = (notification.AbsoluteUtcTime - currentTime).Ticks;
+ notification.NextDueTicks = currentTicks + remainingTicks;
+
+ }
+
+ // Notification processed, move it over to the next priority queue
+ updatedQueue.Enqueue(notification);
+
+ }
+ }
+
+ // Replace the working queue with the update queue
+ this.notifications = updatedQueue;
+ }
+
+ this.notificationWaitEvent.Set();
+ }
+
/// Schedules a notification for processing by the timer thread
/// Notification that will be scheduled
/// The scheduled notification
private object scheduleNotification(Notification notification) {
- lock(this.notifications) {
+ lock(this.timerThread) {
this.notifications.Enqueue(notification);
// If this notification has become that next due notification, wake up
@@ -316,12 +406,15 @@ namespace Nuclex.Support.Scheduling {
/// Executes the timer thread
private void runTimerThread() {
+ Notification nextDueNotification;
+ lock(this.timerThread) {
+ nextDueNotification = getNextDueNotification();
+ }
+ // Keep processing notifications until we're told to quit
for(; ; ) {
- // Get the notification that is due next and wait for it. When no notifications
- // are queued, wait indefinitely until we're signalled
- Notification nextDueNotification = getNextDueNotification();
+ // Wait until the nextmost notification is due or something else wakes us up
if(nextDueNotification == null) {
this.notificationWaitEvent.WaitOne();
} else {
@@ -334,27 +427,52 @@ namespace Nuclex.Support.Scheduling {
break;
}
+ // Process all notifications that are due by handing them over to the thread pool.
+ // The notification queue might have been updated while we were sleeping, so
+ // look for the notification that is due next again
+ long ticks = this.timeSource.Ticks;
+ lock(this.timerThread) {
+ for(; ; ) {
+ nextDueNotification = getNextDueNotification();
+ if(nextDueNotification == null) {
+ break;
+ }
- //if(nextDueNotification.AbsoluteUtcTime !=
+ // If the next notification is more than a millisecond away, we've reached
+ // the end of the notifications we need to process.
+ long remainingTicks = (nextDueNotification.NextDueTicks - ticks);
+ if(remainingTicks >= TicksPerMillisecond) {
+ break;
+ }
- }
+ if(!nextDueNotification.Cancelled) {
+ ThreadPool.QueueUserWorkItem(nextDueNotification.Callback);
+ }
+ this.notifications.Dequeue();
+ if(nextDueNotification.IntervalTicks != 0) {
+ nextDueNotification.NextDueTicks += nextDueNotification.IntervalTicks;
+ this.notifications.Enqueue(nextDueNotification);
+ }
+ } // for
+ } // lock
+
+ } // for
}
/// Retrieves the notification that is due next
/// The notification that is due next
private Notification getNextDueNotification() {
- lock(this.notifications) {
- if(this.notifications.Count == 0) {
- return null;
- } else {
- Notification nextDueNotification = this.notifications.Peek();
- while(nextDueNotification.Cancelled) {
- this.notifications.Dequeue();
- nextDueNotification = this.notifications.Peek();
- }
- return nextDueNotification;
+ if(this.notifications.Count == 0) {
+ return null;
+ } else {
+ Notification nextDueNotification = this.notifications.Peek();
+ while(nextDueNotification.Cancelled) {
+ this.notifications.Dequeue();
+ nextDueNotification = this.notifications.Peek();
}
+
+ return nextDueNotification;
}
}
@@ -370,8 +488,9 @@ namespace Nuclex.Support.Scheduling {
/// Whether the timer thread should end
private volatile bool endRequested;
+ /// Delegate for the dateTimeAdjusted() method
+ private EventHandler dateTimeAdjustedDelegate;
+
}
} // namespace Nuclex.Support.Scheduling
-
-#endif
diff --git a/Source/Scheduling/WindowsTimeSource.cs b/Source/Scheduling/WindowsTimeSource.cs
index fc446b5..cb6f362 100644
--- a/Source/Scheduling/WindowsTimeSource.cs
+++ b/Source/Scheduling/WindowsTimeSource.cs
@@ -18,13 +18,13 @@ License along with this library
*/
#endregion
-#if !XBOX360
-
using System;
using System.Collections.Generic;
using System.Threading;
+#if !XBOX360
using Microsoft.Win32;
+#endif
namespace Nuclex.Support.Scheduling {
@@ -38,16 +38,24 @@ namespace Nuclex.Support.Scheduling {
/// Initializes a new Windows time source
public WindowsTimeSource() {
+#if XBOX360
+ throw new InvalidOperationException(
+ "Windows time source is not available on the XBox 360"
+ );
+#else
this.onDateTimeAdjustedDelegate = new EventHandler(OnDateTimeAdjusted);
SystemEvents.TimeChanged += this.onDateTimeAdjustedDelegate;
+#endif
}
/// Immediately releases all resources owned by the instance
public void Dispose() {
+#if !XBOX360
if(this.onDateTimeAdjustedDelegate != null) {
SystemEvents.TimeChanged -= this.onDateTimeAdjustedDelegate;
this.onDateTimeAdjustedDelegate = null;
}
+#endif
}
/// Waits for an AutoResetEvent to become signalled
@@ -57,7 +65,7 @@ namespace Nuclex.Support.Scheduling {
/// True if the WaitHandle was signalled, false if the timeout was reached
///
public override bool WaitOne(AutoResetEvent waitHandle, long ticks) {
- return waitHandle.WaitOne((int)(ticks / TicksPerMillisecond));
+ return waitHandle.WaitOne((int)(ticks / TicksPerMillisecond), false);
}
///
@@ -73,5 +81,3 @@ namespace Nuclex.Support.Scheduling {
}
} // namespace Nuclex.Support.Scheduling
-
-#endif // !XBOX360