diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj
index d716fe7..151973f 100644
--- a/Nuclex.Support (Xbox 360).csproj	
+++ b/Nuclex.Support (Xbox 360).csproj	
@@ -122,12 +122,15 @@
     
       PrototypeFactory.cs
     
-    
-      DefaultTimeSource.cs
+    
+      GenericTimeSource.cs
     
     
     
-    
+    
+    
+      Scheduler.cs
+    
     
     
       WindowsTimeSource.cs
diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj
index 2d2affa..bf1bc76 100644
--- a/Nuclex.Support.csproj
+++ b/Nuclex.Support.csproj
@@ -104,12 +104,15 @@
     
       PrototypeFactory.cs
     
-    
-      DefaultTimeSource.cs
+    
+      GenericTimeSource.cs
     
     
     
-    
+    
+    
+      Scheduler.cs
+    
     
     
       WindowsTimeSource.cs
diff --git a/Source/Scheduling/DefaultTimeSource.Test.cs b/Source/Scheduling/GenericTimeSource.Test.cs
similarity index 82%
rename from Source/Scheduling/DefaultTimeSource.Test.cs
rename to Source/Scheduling/GenericTimeSource.Test.cs
index 9f2fe2f..c00feaf 100644
--- a/Source/Scheduling/DefaultTimeSource.Test.cs
+++ b/Source/Scheduling/GenericTimeSource.Test.cs
@@ -28,16 +28,16 @@ using NUnit.Framework;
 
 namespace Nuclex.Support.Scheduling {
 
-  /// Unit Test for the default scheduler time source
+  /// Unit Test for the generic scheduler time source
   [TestFixture]
-  public class DefaultTimeSourceTest {
+  public class GenericTimeSourceTest {
 
     /// 
     ///   Verifies that the time source's default constructor is working
     /// 
     [Test]
     public void TestDefaultConstructor() {
-      DefaultTimeSource timeSource = new DefaultTimeSource();
+      GenericTimeSource timeSource = new GenericTimeSource();
     }
 
     /// 
@@ -45,7 +45,7 @@ namespace Nuclex.Support.Scheduling {
     /// 
     [Test]
     public void TestCurrentUtcTime() {
-      DefaultTimeSource timeSource = new DefaultTimeSource();
+      GenericTimeSource timeSource = new GenericTimeSource();
 
       Assert.That(
         timeSource.CurrentUtcTime, Is.EqualTo(DateTime.UtcNow).Within(10).Seconds
@@ -58,7 +58,7 @@ namespace Nuclex.Support.Scheduling {
     /// 
     [Test]
     public void TestTicksWithStopwatch() {
-      DefaultTimeSource timeSource = new DefaultTimeSource(true);
+      GenericTimeSource timeSource = new GenericTimeSource(true);
       long ticks1 = timeSource.Ticks;
       long ticks2 = timeSource.Ticks;
 
@@ -71,7 +71,7 @@ namespace Nuclex.Support.Scheduling {
     /// 
     [Test]
     public void TestTicksWithTickCount() {
-      DefaultTimeSource timeSource = new DefaultTimeSource(false);
+      GenericTimeSource timeSource = new GenericTimeSource(false);
       long ticks1 = timeSource.Ticks;
       long ticks2 = timeSource.Ticks;
 
@@ -83,7 +83,7 @@ namespace Nuclex.Support.Scheduling {
     /// 
     [Test]
     public void TestWaitOne() {
-      DefaultTimeSource timeSource = new DefaultTimeSource();
+      GenericTimeSource timeSource = new GenericTimeSource();
       AutoResetEvent waitEvent = new AutoResetEvent(true);
 
       Assert.IsTrue(timeSource.WaitOne(waitEvent, TimeSpan.FromMilliseconds(1).Ticks));
diff --git a/Source/Scheduling/DefaultTimeSource.cs b/Source/Scheduling/GenericTimeSource.cs
similarity index 67%
rename from Source/Scheduling/DefaultTimeSource.cs
rename to Source/Scheduling/GenericTimeSource.cs
index 52862a1..5ab5998 100644
--- a/Source/Scheduling/DefaultTimeSource.cs
+++ b/Source/Scheduling/GenericTimeSource.cs
@@ -26,13 +26,20 @@ using System.Threading;
 namespace Nuclex.Support.Scheduling {
 
   /// 
-  ///   Default time source implementation using the Stopwatch or Environment.TickCount
+  ///   Generic time source implementation using the Stopwatch or Environment.TickCount
   /// 
-  public class DefaultTimeSource : ITimeSource {
+  public class GenericTimeSource : ITimeSource {
 
     /// Number of ticks (100 ns intervals) in a millisecond
     private const long TicksPerMillisecond = 10000;
 
+    /// Tolerance for the detection of a date/time adjustment
+    /// 
+    ///   If the current system date/time jumps by more than this tolerance into any
+    ///   direction, the default time source will trigger the DateTimeAdjusted event.
+    /// 
+    private const long TimeAdjustmentToleranceTicks = 75 * TicksPerMillisecond;
+
     /// Called when the system date/time are adjusted
     /// 
     ///   An adjustment is a change out of the ordinary, eg. when a time synchronization
@@ -42,13 +49,13 @@ namespace Nuclex.Support.Scheduling {
     public event EventHandler DateTimeAdjusted;
 
     /// Initializes the static fields of the default time source
-    static DefaultTimeSource() {
+    static GenericTimeSource() {
       tickFrequency = 10000000.0;
       tickFrequency /= (double)Stopwatch.Frequency;
     }
 
     /// Initializes the default time source
-    public DefaultTimeSource() : this(Stopwatch.IsHighResolution) { }
+    public GenericTimeSource() : this(Stopwatch.IsHighResolution) { }
 
     /// Initializes the default time source
     /// 
@@ -67,23 +74,25 @@ namespace Nuclex.Support.Scheduling {
     ///     but then your won't profit from the high-resolution timer if one is available.
     ///   
     /// 
-    public DefaultTimeSource(bool useStopwatch) {
+    public GenericTimeSource(bool useStopwatch) {
       this.useStopwatch = useStopwatch;
+
+      checkForTimeAdjustment();
     }
 
-    /// Waits for an AutoResetEvent become signalled
+    /// 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
     /// 
     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);
-
     }
 
     /// Current system time in UTC format
@@ -127,6 +136,41 @@ namespace Nuclex.Support.Scheduling {
       }
     }
 
+    /// 
+    ///   Checks whether the system/date time have been adjusted since the last call
+    /// 
+    private void checkForTimeAdjustment() {
+
+      // Grab the current date/time and timer ticks in one go
+      DateTime currentLocalTime = DateTime.Now;
+      long currentTicks = Ticks;
+
+      // Calculate the number of timer ticks that have passed since the last check and
+      // extrapolate the local date/time we should be expecting to see
+      long ticksSinceLastCheck = currentTicks - lastCheckedTicks;
+      DateTime expectedLocalTime = new DateTime(
+        lastCheckedLocalTime.Ticks + ticksSinceLastCheck, DateTimeKind.Local
+      );
+
+      // Find out by what amount the actual local date/time deviates from
+      // the extrapolated date/time and trigger the date/time adjustment event if
+      // we can reasonably assume that the system date/time have been adjusted.
+      long deviationTicks = Math.Abs(expectedLocalTime.Ticks - currentLocalTime.Ticks);
+      if(deviationTicks > TimeAdjustmentToleranceTicks) {
+        OnDateTimeAdjusted(this, EventArgs.Empty);
+      }
+
+      // Remember the current local date/time and timer ticks for the next run
+      this.lastCheckedLocalTime = currentLocalTime;
+      this.lastCheckedTicks = currentTicks;
+
+    }
+
+    /// Last local time we checked for a date/time adjustment
+    private DateTime lastCheckedLocalTime;
+    /// Timer ticks at which we last checked the local time
+    private long lastCheckedTicks;
+
     /// Number of ticks per Stopwatch time unit
     private static double tickFrequency;
     /// Whether ot use the Stopwatch class for measuring time
diff --git a/Source/Scheduling/ITimeSource.cs b/Source/Scheduling/ITimeSource.cs
index 72dd5c6..beee0b7 100644
--- a/Source/Scheduling/ITimeSource.cs
+++ b/Source/Scheduling/ITimeSource.cs
@@ -25,7 +25,7 @@ using System.Threading;
 namespace Nuclex.Support.Scheduling {
 
   /// Provides time measurement and change notification services
-  interface ITimeSource {
+  public interface ITimeSource {
 
     /// Called when the system date/time are adjusted
     /// 
@@ -35,7 +35,7 @@ namespace Nuclex.Support.Scheduling {
     /// 
     event EventHandler DateTimeAdjusted;
 
-    /// Waits for an AutoResetEvent become signalled
+    /// Waits for an AutoResetEvent to become signalled
     /// WaitHandle the method will wait for
     /// Number of ticks to wait
     /// 
diff --git a/Source/Scheduling/Scheduler.Test.cs b/Source/Scheduling/Scheduler.Test.cs
new file mode 100644
index 0000000..0a6d9f2
--- /dev/null
+++ b/Source/Scheduling/Scheduler.Test.cs
@@ -0,0 +1,105 @@
+#region CPL License
+/*
+Nuclex Framework
+Copyright (C) 2002-2009 Nuclex Development Labs
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the IBM Common Public License as
+published by the IBM Corporation; either version 1.0 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+IBM Common Public License for more details.
+
+You should have received a copy of the IBM Common Public
+License along with this library
+*/
+#endregion
+
+#if UNITTEST
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using Microsoft.Win32;
+
+using NUnit.Framework;
+
+#if false
+
+namespace Nuclex.Support.Scheduling {
+
+  /// Unit Test for the scheduler
+  [TestFixture]
+  public class SchedulerTest {
+
+    /// 
+    ///   Test whether the Scheduler can explicitely create a windows time source
+    /// 
+    [Test]
+    public void TestCreateWindowsTimeSource() {
+      ITimeSource timeSource = Scheduler.CreateTimeSource(true);
+      try {
+        Assert.That(timeSource is WindowsTimeSource);
+      }
+      finally {
+        IDisposable disposableTimeSource = timeSource as IDisposable;
+        if(disposableTimeSource != null) {
+          disposableTimeSource.Dispose();
+        }
+      }
+    }
+
+    /// 
+    ///   Test whether the Scheduler can explicitely create a generic time source
+    /// 
+    [Test]
+    public void TestCreateGenericTimeSource() {
+      ITimeSource timeSource = Scheduler.CreateTimeSource(false);
+      try {
+        Assert.That(timeSource is GenericTimeSource);
+      }
+      finally {
+        IDisposable disposableTimeSource = timeSource as IDisposable;
+        if(disposableTimeSource != null) {
+          disposableTimeSource.Dispose();
+        }
+      }
+    }
+
+    /// 
+    ///   Test whether the Scheduler can automatically choose the right time source
+    /// 
+    [Test]
+    public void TestCreateDefaultTimeSource() {
+      ITimeSource timeSource = Scheduler.CreateDefaultTimeSource();
+      try {
+        Assert.IsNotNull(timeSource);
+      }
+      finally {
+        IDisposable disposableTimeSource = timeSource as IDisposable;
+        if(disposableTimeSource != null) {
+          disposableTimeSource.Dispose();
+        }
+      }
+    }
+
+    /// 
+    ///   Verifies that the default constructor of the scheduler is working
+    /// 
+    [Test]
+    public void TestDefaultConstructor() {
+      using(Scheduler scheduler = new Scheduler()) { }
+    }
+
+  }
+
+} // namespace Nuclex.Support.Scheduling
+
+#endif
+
+#endif // UNITTEST
diff --git a/Source/Scheduling/Scheduler.cs b/Source/Scheduling/Scheduler.cs
index f13fa24..0363553 100644
--- a/Source/Scheduling/Scheduler.cs
+++ b/Source/Scheduling/Scheduler.cs
@@ -23,20 +23,355 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Diagnostics;
 
-namespace Nuclex.Support.Scheduling {
+using Nuclex.Support.Collections;
 
 #if false
 
+namespace Nuclex.Support.Scheduling {
+
   /// Schedules actions for execution at a future point in time
   public class Scheduler : IDisposable {
 
-    /// Immediately releases all resources owned by the instance
-    public void Dispose() {
+    /// One tick is 100 ns, meaning 10000 ticks equal 1 ms
+    private const long TicksPerMillisecond = 10000;
+
+#region class TimeSourceSingleton
+
+    /// 
+    ///   Manages the singleton instance of the scheduler's default time source
+    /// 
+    private class TimeSourceSingleton {
+
+      /// 
+      ///   Explicit static constructor to guarantee the singleton is initialized only
+      ///   when a static member of this class is accessed.
+      /// 
+      static TimeSourceSingleton() { } // Do not remove!
+
+      /// The singleton instance of the default time source
+      internal static readonly ITimeSource Instance = Scheduler.CreateDefaultTimeSource();
+
     }
 
+    #endregion // class TimeSourceSingleton
+
+#region class Notification
+
+    /// Scheduled notification
+    private class Notification {
+
+      /// Initializes a new notification
+      /// 
+      ///   Interval in which the notification will re-executed
+      /// 
+      /// 
+      ///   Time source ticks the notification is next due at
+      /// 
+      /// 
+      ///   Absolute time in UTC at which the notification is due
+      /// 
+      /// 
+      ///   Callback to be invoked when the notification is due
+      /// 
+      public Notification(
+        long intervalTicks,
+        long nextDueTicks,
+        DateTime absoluteUtcTime,
+        Delegate callback
+      ) {
+        this.IntervalTicks = intervalTicks;
+        this.NextDueTicks = nextDueTicks;
+        this.AbsoluteUtcTime = absoluteUtcTime;
+        this.Callback = callback;
+        this.Cancelled = false;
+      }
+
+      /// 
+      ///   Ticks specifying the interval in which the notification will be re-executed
+      /// 
+      public long IntervalTicks;
+
+      /// Next due time for this notification
+      public long NextDueTicks;
+      /// Absolute time in UTC at which the notification is due
+      /// 
+      ///   Only stored for notifications scheduled in absolute time, meaning they
+      ///   have to be adjusted if the system date/time changes
+      /// 
+      public DateTime AbsoluteUtcTime;
+      /// Callback that will be invoked when the notification is due
+      public Delegate Callback;
+      /// Whether the notification has been cancelled
+      public bool Cancelled;
+
+    }
+
+    #endregion // class Notification
+
+    /// 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.timeSource = timeSource;
+      this.notifications = new PriorityQueue();
+      this.notificationWaitEvent = new AutoResetEvent(false);
+
+      this.timerThread = new Thread(new ThreadStart(runTimerThread));
+      this.timerThread.Name = "Nuclex.Support.Scheduling.Scheduler";
+      this.timerThread.Priority = ThreadPriority.Highest;
+      this.timerThread.IsBackground = true;
+      this.timerThread.Start();
+    }
+
+    /// Immediately releases all resources owned by the instance
+    public void Dispose() {
+      if(this.timerThread != null) {
+        this.endRequested = true;
+        this.notificationWaitEvent.Set();
+
+        // Wait for the timer thread to exit. If it doesn't exit in 10 seconds (which is
+        // a lot of time given that it doesn't do any real work), forcefully abort
+        // the thread. This may risk some leaks, but it's the only thing we can do.
+        Trace.Assert(
+          this.timerThread.Join(2500), "Scheduler timer thread did not exit in time"
+        );
+
+        // Get rid of the notification wait event now that we've made sure that
+        // the timer thread is down.
+        this.notificationWaitEvent.Close();
+
+        // 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.
+        this.timerThread = null;
+      }
+    }
+
+    /// Schedules a notification at the specified absolute time
+    /// 
+    ///   Absolute time at which the notification will occur
+    /// 
+    /// 
+    ///   Callback that will be invoked when the notification is due
+    /// 
+    /// A handle that can be used to cancel the notification
+    /// 
+    ///   The notification is scheduled for the indicated absolute time. If the system
+    ///   enters/leaves daylight saving time or the date/time is changed (for example
+    ///   when the system synchronizes with an NTP server), this will affect
+    ///   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) {
+      if(notificationTime.Kind == DateTimeKind.Unspecified) {
+        throw new ArgumentException(
+          "Notification time is neither UTC or local", "notificationTime"
+        );
+      }
+
+      DateTime notificationTimeUtc = notificationTime.ToUniversalTime();
+      long remainingTicks = notificationTimeUtc.Ticks - DateTime.UtcNow.Ticks;
+      long nextDueTicks = this.timeSource.Ticks + remainingTicks;
+
+      return scheduleNotification(
+        new Notification(
+          0,
+          nextDueTicks,
+          notificationTimeUtc,
+          callback
+        )
+      );
+    }
+
+    /// Schedules a notification after the specified time span
+    /// Delay after which the notification will occur
+    /// 
+    ///   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) {
+      return scheduleNotification(
+        new Notification(
+          0,
+          delay.Ticks,
+          DateTime.MinValue,
+          callback
+        )
+      );
+    }
+
+    /// 
+    ///   Schedules a notification after the specified amount of milliseconds
+    /// 
+    /// 
+    ///   Number of milliseconds after which the notification will occur
+    /// 
+    /// 
+    ///   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) {
+      return scheduleNotification(
+        new Notification(
+          0,
+          (long)delayMilliseconds * TicksPerMillisecond,
+          DateTime.MinValue,
+          callback
+        )
+      );
+    }
+
+    /// 
+    ///   Schedules a recurring notification after the specified time span
+    /// 
+    /// Delay after which the first notification will occur
+    /// Interval at which the notification will be repeated
+    /// 
+    ///   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) {
+      return scheduleNotification(
+        new Notification(
+          interval.Ticks,
+          delay.Ticks,
+          DateTime.MinValue,
+          callback
+        )
+      );
+    }
+
+    /// 
+    ///   Schedules a recurring notification after the specified amount of milliseconds
+    /// 
+    /// 
+    ///   Milliseconds after which the first notification will occur
+    /// 
+    /// 
+    ///   Interval in milliseconds at which the notification will be repeated
+    /// 
+    /// 
+    ///   Callback that will be invoked when the notification is due
+    /// 
+    /// A handle that can be used to cancel the notification
+    public object NotifyEach(
+      int delayMilliseconds, int intervalMilliseconds, Delegate callback
+    ) {
+      return scheduleNotification(
+        new Notification(
+          (long)intervalMilliseconds * TicksPerMillisecond,
+          (long)delayMilliseconds * TicksPerMillisecond,
+          DateTime.MinValue,
+          callback
+        )
+      );
+    }
+
+    /// Returns the default time source for the scheduler
+    public static ITimeSource DefaultTimeSource {
+      get { return TimeSourceSingleton.Instance; }
+    }
+
+    /// Creates a new default time source for the scheduler
+    /// 
+    ///   Whether the specialized windows time source should be used
+    /// 
+    /// The newly created time source
+    internal static ITimeSource CreateTimeSource(bool useWindowsTimeSource) {
+      if(useWindowsTimeSource) {
+        return new WindowsTimeSource();
+      } else {
+        return new GenericTimeSource();
+      }
+    }
+
+    /// Creates a new default time source for the scheduler
+    /// The newly created time source
+    internal static ITimeSource CreateDefaultTimeSource() {
+      return CreateTimeSource(WindowsTimeSource.Available);
+    }
+
+    /// 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) {
+        this.notifications.Enqueue(notification);
+
+        // If this notification has become that next due notification, wake up
+        // the timer thread so it can adjust its sleep period.
+        if(ReferenceEquals(this.notifications.Peek(), notification)) {
+          this.notificationWaitEvent.Set();
+        }
+      }
+
+      return notification;
+    }
+
+    /// Executes the timer thread
+    private void runTimerThread() {
+
+      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();
+        if(nextDueNotification == null) {
+          this.notificationWaitEvent.WaitOne();
+        } else {
+          long remainingTicks = nextDueNotification.NextDueTicks - this.timeSource.Ticks;
+          this.timeSource.WaitOne(this.notificationWaitEvent, remainingTicks);
+        }
+
+        // Have we been woken up because the Scheduler is being disposed?
+        if(this.endRequested) {
+          break;
+        }
+
+
+        //if(nextDueNotification.AbsoluteUtcTime !=         
+
+      }
+
+    }
+
+    /// 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;
+        }
+      }
+    }
+
+    /// Time source used by the scheduler
+    private ITimeSource timeSource;
+    /// Thread that will wait for the next scheduled event
+    private Thread timerThread;
+    /// Notifications in the scheduler's queue
+    private PriorityQueue notifications;
+
+    /// Event used by the timer thread to wait for the next notification
+    private AutoResetEvent notificationWaitEvent;
+    /// Whether the timer thread should end
+    private volatile bool endRequested;
 
   }
 
-#endif
-
 } // namespace Nuclex.Support.Scheduling
+
+#endif
diff --git a/Source/Scheduling/WindowsTimeSource.cs b/Source/Scheduling/WindowsTimeSource.cs
index 0747c87..fc446b5 100644
--- a/Source/Scheduling/WindowsTimeSource.cs
+++ b/Source/Scheduling/WindowsTimeSource.cs
@@ -31,7 +31,7 @@ namespace Nuclex.Support.Scheduling {
   /// 
   ///   Time source that makes use of additional features only available on Windows
   /// 
-  public class WindowsTimeSource : DefaultTimeSource, IDisposable {
+  public class WindowsTimeSource : GenericTimeSource, IDisposable {
 
     /// Number of ticks (100 ns intervals) in a millisecond
     private const long TicksPerMillisecond = 10000;
@@ -50,7 +50,7 @@ namespace Nuclex.Support.Scheduling {
       }
     }
 
-    /// Waits for an AutoResetEvent become signalled
+    /// Waits for an AutoResetEvent to become signalled
     /// WaitHandle the method will wait for
     /// Number of ticks to wait
     ///