Thursday, October 20, 2011

Internet Synchronized Clock using C#. Part 2

In previous part we got some methods just to get time from internet. But this may be not enough for the real clock. Let’s tell us current UTC time and will synchronize with internet servers from time to time to ensure accuracy.
First of all, lets put all methods from previous part inside some class, let it be called InternetTimeGetter.
We want to have clock which is synchronized with internet. Since synchronization may take some time or even fail we don’t want our application to freeze while performing synchronization. This means that we need background thread for this. We may use Task class for this. Also we will need some variables to keep current time value and last synchronization time. Let’s start

Variables
  1. /// <summary>
  2. /// Last synchronization time.
  3. /// </summary>
  4. private DateTime _lastSyncTime;
  5. /// <summary>
  6. /// Current time value.
  7. /// </summary>
  8. private DateTime _currentTime;
  9. /// <summary>
  10. /// Task used for internet synchronization.
  11. /// </summary>
  12. private Task _syncTask;

To do something with Task we should initialize it.

Initialization
  1. public InternetTimeNotifier()
  2. {
  3.   _syncTask = new Task( SyncTaskAction );
  4. }

Points to consider:

1) There is no sense to keep synchronizing as often as we can – it takes resources (internet traffic, CPU time) and also we could be blocked by the server for doing this. This means that we need some synchronization interval.

2) If we perform check after check after check we could get high CPU usage. I don’t think that users or customer will appreciate this. This means that our thread should sleep between checks.

3) But we also should react on pending cancellation (for example, when application is closed), so we cannot sleep all the time between checks.

This resulted in following model – we perform check every second whether cancellation is pending and if not then check whether interval passed and if yes, then perform synchronization. This looks like this:

Synchronization constants
  1. /// <summary>
  2. /// Synchronization interval in seconds.
  3. /// </summary>
  4. private const int SyncIntervalS = 60;
  5. /// <summary>
  6. /// Sleep interval in milliseconds.
  7. /// </summary>
  8. private const int SleepTimeMs = 1000;

Sync method
  1. /// <summary>
  2. /// Synchronization background thread.
  3. /// </summary>
  4. /// <param name="sender"></param>
  5. /// <param name="e"></param>
  6. private void _syncWorker_DoWork( object sender, DoWorkEventArgs e )
  7. {
  8.   while( !_cancelled )
  9.   {
  10.     DateTime now = DateTime.Now;
  11.     double elapsedSeconds = ( _lastSyncTime - now ).TotalSeconds;
  12.  
  13.     if( elapsedSeconds >= SyncIntervalS )
  14.     {
  15.       _currentTime = InternetTimeGetter.GetCurrentUtcTime();
  16.       _lastSyncTime = DateTime.Now;
  17.     }
  18.  
  19.     Thread.Sleep( SleepTimeMs );
  20.   }
  21. }

Now we can sync with internet. That’s great, but clock usually ticks every second. To achieve this we need another background thread (I think we don’t want ticks stop during synchronization).
Task and its method for this task looks very similar to the one we have here:

Tick variables
  1. /// <summary>
  2. /// Stores last tick time.
  3. /// </summary>
  4. private DateTime _lastTickTime;
  5. /// <summary>
  6. /// Task responsible for clock ticks.
  7. /// </summary>
  8. private Task _tickTask = new Task();

Initialization for constructor
  1. _tickTask = new Task( ( Action )TickTaskAction );

Tick method
  1. private const int TickIntervalMS = 1000;
  2.  
  3. private void TickTaskAction( object sender, DoWorkEventArgs e )
  4. {
  5.   while( !_cancelled )
  6.   {
  7.     DateTime now = DateTime.Now;
  8.     double elapsedMilliseconds = ( now - _lastTickTime ).TotalMilliseconds;
  9.  
  10.     if( _lastTickTime.Ticks > 0 && elapsedMilliseconds >= TickIntervalMS )
  11.     {
  12.       _lastTickTime = now;
  13.       _currentTime = _currentTime.AddMilliseconds( elapsedMilliseconds );
  14.     }
  15.     else
  16.     {
  17.       Thread.Sleep( Math.Max( 0, ( int )( TickIntervalMS - elapsedMilliseconds ) ) );
  18.     }
  19.   }
  20. }

Generally this may work, however you could notice potential problem – these are separate threads and they can be stopped/started by OS at any time so there is potential danger that at some moment there will be switch between these threads, just at the when milliseconds were added in tick method, but value wasn’t assigned yet. And synch thread can update _currentTime moment at that moment and then tick thread will overwrite it with incorrect value.
lock statement should help us here. To use it we will need some object that we will lock. In our particular situation we could use this as object passed to lock statement, but generally we may need several different lock statements for different methods, so I prefer to explicitly create object that will be locked.

  1. /// <summary>
  2. /// Object used to synch time updates.
  3. /// </summary>
  4. private object _updateTimeLocker = new object();
  5. /// <summary>
  6. /// Sets time to the specified value.
  7. /// </summary>
  8. /// <param name="time"></param>
  9. private void SetTime( DateTime time )
  10. {
  11.   lock( _updateTimeLocker )
  12.   {
  13.     _currentTime = time;
  14.   }
  15. }

Now it looks better (don’t forget to update above methods to use SetTime instead of setting _currentTime directy). It still not ideal and sometimes we may potentially overwrite our synchronized value, but it is better than it was before. Maybe I’ll update this thing in next part.
And now the most important thing. We should give user ability to start timer, close it when it is not needed and notify about time changes.

First thing is achieved by simply calling corresponding methods of Task objects:

Start
  1. /// <summary>
  2. /// Starts time notification.
  3. /// </summary>
  4. public void Start()
  5. {
  6.   _syncTask.Start();
  7.   _tickTask.Start();
  8.  
  9. }

And freeing memory is quite trivial too:

Dispose
  1. public void Dispose()
  2. {
  3.   if( _syncTask != null )
  4.   {
  5.     _canceled = true;
  6.  
  7.     while( !_syncTask.IsCanceled && !_syncTask.IsCompleted && !_syncTask.IsFaulted )
  8.       Thread.Sleep( 100 );
  9.  
  10.     while( !_syncTask.IsCanceled && !_syncTask.IsCompleted && !_syncTask.IsFaulted )
  11.       Thread.Sleep( 100 );
  12.  
  13.     _syncTask.Dispose();
  14.     _tickTask.Dispose();
  15.  
  16.     _syncTask = null;
  17.     _tickTask = null;
  18.  
  19.     GC.SuppressFinalize( this );
  20.   }
  21. }
  22. ~InternetTimeNotifier()
  23. {
  24.   Dispose();
  25. }

Now we need to introduce event handler and event arguments, for example, like this:

Event declaration
  1. public delegate void ValueChangedEventHandler( object sender, ValueChangedEventArgs args );
  2.  
  3. public class ValueChangedEventArgs : EventArgs
  4. {
  5.   private DateTime _value;
  6.  
  7.   public DateTime Value
  8.   {
  9.     get
  10.     {
  11.       return _value;
  12.     }
  13.   }
  14.  
  15.   public ValueChangedEventArgs( DateTime value )
  16.   {
  17.     _value = value;
  18.   }
  19. }

After this everything we need is just to call event inside SetTime method:

  1. if( TimeChanged != null )
  2.   TimeChanged.BeginInvoke( this, new ValueChangedEventArgs( _currentTime ), null, null );


We use BeginInvoke method execute event in background thread. This ensures that our threads won't freeze because of user event handler. Just don't forget about this when writing event handler which updates some control and use Disptacher for WPF.

And to finish this article final version of source codes used by it:

InternetTimeNotifier
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.ComponentModel;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8.  
  9. namespace InternetUtcTime
  10. {
  11.   public class InternetTimeNotifier
  12.   {
  13.     #region Constants
  14.     /// <summary>
  15.     /// Synchronization interval in seconds.
  16.     /// </summary>
  17.     private const int SyncIntervalS = 60;
  18.     /// <summary>
  19.     /// Sleep interval in milliseconds.
  20.     /// </summary>
  21.     private const int SleepTimeMs = 1000;
  22.  
  23.     /// <summary>
  24.     /// Tick interval.
  25.     /// </summary>
  26.     private const int TickIntervalMS = 1000;
  27.     #endregion
  28.  
  29.     #region Members
  30.     /// <summary>
  31.     /// Last synchronization time.
  32.     /// </summary>
  33.     private DateTime _lastSyncTime;
  34.     /// <summary>
  35.     /// Current time value.
  36.     /// </summary>
  37.     private DateTime _currentTime;
  38.     /// <summary>
  39.     /// Task used for internet synchronization.
  40.     /// </summary>
  41.     private Task _syncTask;
  42.  
  43.     /// <summary>
  44.     /// Stores last tick time.
  45.     /// </summary>
  46.     private DateTime _lastTickTime;
  47.     /// <summary>
  48.     /// Task responsible for clock ticks.
  49.     /// </summary>
  50.     private Task _tickTask;
  51.  
  52.     /// <summary>
  53.     /// Object used to synch time updates.
  54.     /// </summary>
  55.     private object _updateTimeLocker = new object();
  56.     private bool _cancelled;
  57.     #endregion
  58.  
  59.     #region Methods
  60.     public InternetTimeNotifier()
  61.     {
  62.       _syncTask = new Task( SyncTaskAction );
  63.       _tickTask = new Task( ( Action )TickTaskAction );
  64.     }
  65.  
  66.     private void TickTaskAction()
  67.     {
  68.       while( !_cancelled )
  69.       {
  70.         DateTime now = DateTime.Now;
  71.         double elapsedMilliseconds = ( now - _lastTickTime ).TotalMilliseconds;
  72.  
  73.         if( _lastTickTime.Ticks > 0 && elapsedMilliseconds >= TickIntervalMS )
  74.         {
  75.           _lastTickTime = now;
  76.           SetTime( _currentTime.AddMilliseconds( elapsedMilliseconds ) );
  77.         }
  78.         else
  79.         {
  80.           Thread.Sleep( Math.Max( 0, ( int )( TickIntervalMS - elapsedMilliseconds ) ) );
  81.         }
  82.       }
  83.     }
  84.  
  85.     /// <summary>
  86.     /// Synchronization background thread.
  87.     /// </summary>
  88.     /// <param name="sender"></param>
  89.     /// <param name="e"></param>
  90.     private void SyncTaskAction()
  91.     {
  92.       while( !_cancelled )
  93.       {
  94.         DateTime now = DateTime.Now;
  95.         double elapsedSeconds = ( now - _lastSyncTime ).TotalSeconds;
  96.  
  97.         if( elapsedSeconds >= SyncIntervalS )
  98.         {
  99.           SetTime( InternetTimeGetter.GetCurrentUtcTime() );
  100.           _lastTickTime = _lastSyncTime = DateTime.Now;
  101.         }
  102.  
  103.         Thread.Sleep( SleepTimeMs );
  104.       }
  105.     }
  106.     /// <summary>
  107.     /// Sets time to the specified value.
  108.     /// </summary>
  109.     /// <param name="time"></param>
  110.     private void SetTime( DateTime time )
  111.     {
  112.       lock( _updateTimeLocker )
  113.       {
  114.         _currentTime = time;
  115.       }
  116.  
  117.       if( TimeChanged != null )
  118.         TimeChanged.BeginInvoke( this, new ValueChangedEventArgs( _currentTime ), null, null );
  119.     }
  120.     /// <summary>
  121.     /// Starts time notification.
  122.     /// </summary>
  123.     public void Start()
  124.     {
  125.       _syncTask.Start();
  126.       _tickTask.Start();
  127.  
  128.     }
  129.     public event ValueChangedEventHandler TimeChanged;
  130.     public void Dispose()
  131.     {
  132.       if( _syncTask != null )
  133.       {
  134.         _cancelled = true;
  135.  
  136.         while( !_syncTask.IsCanceled && !_syncTask.IsCompleted && !_syncTask.IsFaulted )
  137.           Thread.Sleep( 100 );
  138.  
  139.         while( !_syncTask.IsCanceled && !_syncTask.IsCompleted && !_syncTask.IsFaulted )
  140.           Thread.Sleep( 100 );
  141.  
  142.         _syncTask.Dispose();
  143.         _tickTask.Dispose();
  144.  
  145.         _syncTask = null;
  146.         _tickTask = null;
  147.  
  148.         GC.SuppressFinalize( this );
  149.       }
  150.     }
  151.     ~InternetTimeNotifier()
  152.     {
  153.       Dispose();
  154.     }
  155.     #endregion
  156.   }
  157. }

InternetTimeGetter
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Net.Sockets;
  6. using System.IO;
  7. using System.Globalization;
  8.  
  9. namespace InternetUtcTime
  10. {
  11.   public class InternetTimeGetter
  12.   {
  13.     #region Members
  14.     /// <summary>
  15.     /// Number of allowed iterations
  16.     /// </summary>
  17.     private const int IterationCount = 1000;
  18.     /// <summary>
  19.     /// List of time servers taken from http://tf.nist.gov/tf-cgi/servers.cgi
  20.     /// </summary>
  21.     private static readonly string[] ServersList = new string[]
  22.     {
  23.       "64.90.182.55",
  24.       "96.47.67.105",
  25.       "206.246.122.250",
  26.     };
  27.     #endregion
  28.  
  29.     private static string GetTimeString( string serverName )
  30.     {
  31.       string result = string.Empty;
  32.  
  33.       try
  34.       {
  35.         TcpClient client = new TcpClient();
  36.         client.Connect( serverName, 13 );
  37.  
  38.         Stream stream = client.GetStream();
  39.         int BufferSize = 100;
  40.         byte[] buffer = new byte[ BufferSize ];
  41.         int iReadCount;
  42.  
  43.         while( ( iReadCount = stream.Read( buffer, 0, BufferSize ) ) > 0 )
  44.         {
  45.           result += Encoding.ASCII.GetString( buffer, 0, iReadCount );
  46.         }
  47.  
  48.         stream.Dispose();
  49.         client.Close();
  50.  
  51.       }
  52.       catch
  53.       {
  54.         result = null;
  55.       }
  56.  
  57.       return result;
  58.     }
  59.  
  60.     private static DateTime ParseTimeString( string dateTimeString )
  61.     {
  62.       try
  63.       {
  64.         //55741 11-06-29 11:58:55 50 0 0 629.4 UTC
  65.         dateTimeString = dateTimeString.Trim();
  66.         string[] dateTimeParts = dateTimeString.Split( ' ' );
  67.  
  68.         DateTime result = ( dateTimeParts[ 5 ] == "0" ) ?
  69.           DateTime.ParseExact( dateTimeParts[ 1 ] + ' ' + dateTimeParts[ 2 ], "yy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture ) :
  70.           DateTime.MinValue;
  71.         return result;
  72.       }
  73.       catch
  74.       {
  75.         return DateTime.MinValue;
  76.       }
  77.     }
  78.  
  79.     public static DateTime GetCurrentUtcTime()
  80.     {
  81.       string dateTime = null;
  82.       int index = 0;
  83.       int iteration = 0;
  84.       DateTime typedDateTime = DateTime.MinValue;
  85.  
  86.       do
  87.       {
  88.         do
  89.         {
  90.           dateTime = GetTimeString( ServersList[ index ] );
  91.           index++;
  92.           iteration++;
  93.  
  94.           if( index >= ServersList.Length )
  95.             index = 0;
  96.         }
  97.         while( string.IsNullOrEmpty( dateTime ) && iteration < IterationCount );
  98.  
  99.         typedDateTime = ParseTimeString( dateTime );
  100.       }
  101.       while( typedDateTime == DateTime.MinValue && iteration < IterationCount );
  102.  
  103.       return typedDateTime;
  104.     }
  105.   }
  106. }

ValueChangedEventArgs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace InternetUtcTime
  7. {
  8.   public delegate void ValueChangedEventHandler( object sender, ValueChangedEventArgs args );
  9.  
  10.   public class ValueChangedEventArgs : EventArgs
  11.   {
  12.     private DateTime _value;
  13.  
  14.     public DateTime Value
  15.     {
  16.       get
  17.       {
  18.         return _value;
  19.       }
  20.     }
  21.  
  22.     public ValueChangedEventArgs( DateTime value )
  23.     {
  24.       _value = value;
  25.     }
  26.   }
  27. }

No comments:

Post a Comment