Wednesday, March 14, 2012

Using AutoResetEvent for controlling .NET Windows Service Start and Stop

Problem:
You have to implement a polling service that does a certain task every 'n' units of time till it is stopped.

Pretext:
The methods of interest in this problem are OnStart, OnStop and the real worker thread that we implement. So I would be concentrating on these and will skip out on the boiler plate code required to setup the service. I would be demonstrating only the relevant parts of code in this discussion. Also, the example code here would lack exception handling, thread syncing, for the sake of staying on track with explaining the better solution.

Solutions:
1. Implement a worker thread that does the actual work. Inside the worker thread, implement a sleep loop.

Here is some sample code demonstrating this:

public partial class SampleService : ServiceBase
{
           Thread _worker;
           bool _shouldStop;
           //service start
          protected override void OnStart(string[] args)
         {
                    _worker = new Thread(new ThreadStart(DoWork));
                    _worker.Start();
         }

        //service stopped
        protected override void OnStop()
        {
               _shouldStop = true;
        }

        private void DoWork()
        {
               while(!_shouldStop)
               {
                    //do work here
                    Thread.Sleep(1000); //using 1 sec here. but this can be any configurable value.
               }
        }
}

The basic problem with this solution is as follows:
If the sleep time was a larger value (which is the most common case), for example 5 minutes or 300 secs, then the service would stop responding if someone stopped the service while it was in the middle of the sleep statement. Essentially what happens is that the operating system waits for the worker thread to complete before it can dispose off the service process. Because the worker thread is in a sleep mode, and there is no implementation done to interrupt it, it never returns before the sleep timeout is over. Hence, after a certain period of time, the service goes in a not responding state.
This is just a basic explanation of what might happen in this case, there are other scenarios that might happen with different outcomes, but this is the most common one.

2. Use AutoResetEvent to control the service and the worker thread. This is a better approach, as this gives more control on the service lifetime and allows a clean way to stop the service. 

Here is some sample code demonstrating this approach:

   //Helper class, that wraps the AutoResetEvents.
   //Having this class makes the code a bit cleaner and easier to understand structurally.

    public class ThreadHelper
    {
        public ThreadHelper() { }

        //false in the constructor below signifies not signaled state
        private AutoResetEvent _stopEvent = new AutoResetEvent(false);
        private AutoResetEvent _waitToFinishEvent = new AutoResetEvent(false);
        private Thread _thread = null;

        public AutoResetEvent stopEvent
        {
            set { _stopEvent = value; }
            get { return _stopEvent; }
        }
        public AutoResetEvent waitToFinishEvent
        {
            set { _waitToFinishEvent = value; }
            get { return _waitToFinishEvent; }
        }
        public Thread thread
        {
            set { _thread = value; }
            get { return _thread; }
        }
    }



public partial class SampleService : ServiceBase
{
           ThreadHelper _worker = new ThreadHelper();

           //service start
          protected override void OnStart(string[] args)
         {
                    _worker.thread = new Thread(new ThreadStart(DoWork));
                    _worker.thread.Start();
         }

        //service stopped
        protected override void OnStop()
        {
             if ((_worker.thread != null) && (_worker.thread.IsAlive == true) && (_worker.stopEvent != null))
            {
                 _worker.stopEvent.Set(); //signal DoWork thread to stop
                 //15000 [15 secs] can be changed to any value that you want to let the worker thread finish
                 if (!_worker.waitToFinishEvent.WaitOne(15000, false))
                {
                       //Even after waiting 15000 [15 secs] (or any value that you choose), the worker did not finish
                      //cannot wait any more, so abort the worker thread
                       _worker.thread.Abort();
                }
            }
        }

        private void DoWork()
        {
               bool shouldStop = false;
               while(!shouldStop)
               {
                        if (_worker.stopEvent.WaitOne(/*your polling time*/, false))
                       {
                             shouldStop = true;
                            _worker.waitToFinishEvent.Set();
                       }
                       else
                       {
                             //do work here
                       }
               }
        }
}


An interesting thing to note is that OnStart and OnStop are on the main thread and DoWork is on another thread. Also, the WaitOne method returns true if a signal was received and false if it was not. Specifying a wait time as the first parameter to the WaitOne method causes it to wait for a signal till the wait time is completed. If a signal is not received within the specified wait time, it returns false.

In essence, what happens in this case is that OnStop, signals the stopEvent, which causes the WaitOne (in DoWork thread) to return true (because it was signaled). This causes the shouldStop to be set to true and we come out of the while loop causing the method and hence the thread to complete. On the other hand, after setting shouldStop to true, we signal waitToFinish, which causes the OnStop to complete [because WaitOne in the OnStop returns true and the statements inside if are not evaluated]. In case, if our worker thread was doing the work and it took longer than 15 sec to respond the OnStop WaitOne would return false and it would abort the worker thread.
In any case, this would never cause our service to go in a not responding state.

Although, the explanation here might seem a bit complex, relating with the sample code should help make the concept more clearer.

Happy Coding!!

Make Custom Gifts at CafePress

Friday, March 9, 2012

Calculating DateTime in different TimeZones

Problem:
In a web request, you know the location where the user is coming from, and you have to figure out the current time in the users timezone.

Notes:
To keep this example simple, I've made an assumption that we'll be calculating times only in US time zones. You can also use IP geocoding to figure out the location where the request is coming from and modify the code in this example accordingly.

Solution:
.net framework base class library provides for System.TimeZoneInfo class that can be used for doing timezone based calculations. The idea is as follows:
1. Convert the given time to a Utctime
2. Figure out users location. This can be done by
             a. Geocoding the IP
             b. If the users location is already known (through some profile data already available in the system)
3. Figure out the timezone applicable as per the users location
4. Convert UTC time from step 1 above to the time in users timezone by using TimezoneInfo.ConvertTimeFromUtc method.
The ConvertTimeFromUtc method is intelligent enough to apply daylight savings if applicable to the converted time.

Sample Code:

public class TimezoneComputation
{
          public TimezoneComputation(){}
       
        public virtual DateTime ConvertDateTimeToLocalDateTime(DateTime input,
                                                                                                    string timezoneShortName)
         {
                if (string.IsNullOrEmpty(timezone) == false)
                {
                    var tz = TimeZoneInfo.FindSystemTimeZoneById( timezoneShortName .ToFullTimeZoneName());
                    var tempDt = TimeZoneInfo.ConvertTimeToUtc(input);
                    return TimeZoneInfo.ConvertTimeFromUtc(tempDt, tz);
                }
                //by default return the input without any conversions
                return input;                  
         }
}
    //this helper class adds an extension method to string which returns the full timezone name
   //that can be used by the TimeZoneInfo.FindSystemTimeZoneById method
   //right now this has only the US time zones, but can be extended to include other timezones also

    internal static class TimeZoneMapper
    {
        public static string ToFullTimeZoneName(this string shortName)
        {
            switch (shortName.ToLower())
            {
                case "cst":
                    return "Central Standard Time";
                case "pst":
                    return "Pacific Standard Time";
                case "akst":
                    return "Alaskan Standard Time";
                case "mst":
                    return "Mountain Standard Time";
                case "est":
                    return "Eastern Standard Time";
                case "hst":
                    return "Hawaiian Standard Time";
                case "asst":
                    return "Samoa Standard Time";
                case "ast":
                    return "Atlantic Standard Time";
            }

            return "";
        }
    }

Further Remarks:
In the TimeZoneMapper helper class above, I return the long names (system identifiers) of different timezones. You can find this list here.

Let me know if you want to see how I figured out what to pass in the timezoneShortName parameter of TimezoneComputation.ConvertDateTimeToLocalDateTime method.

The sample code shown above does need some more error checking, for example for edge cases when input is mindatetime, which can be easily implemented.

Here is a sample unit test class (I am using nUnit here):

    [TestFixture]
    public class TimezoneComputationTests
    {

        #region ConvertDateTimeToLocalDateTime Tests

        [Test]
        public void ConvertDateTimeToLocalDateTimeReturnsTheCorrectTimeByTimeZoneShortName()
        {
            //check EST. 2/29/2012 11:10:00 CST should be 2/29/2012 12:10:00 EST
            DateTime cstTime = new DateTime(2012, 2, 29, 11, 10, 0);
            DateTime dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "est");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(12, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check CST. 2/29/2012 11:10:00 CST should be 2/29/2012 11:10:00 CST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "cst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(11, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check PST. 2/29/2012 11:10:00 CST should be 2/29/2012 9:10:00 PST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "pst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(9, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check MST. 2/29/2012 11:10:00 CST should be 2/29/2012 10:10:00 MST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "mst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(10, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check AKST. 2/29/2012 11:10:00 CST should be 2/29/2012 8:10:00 AKST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "akst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(8, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check HST. 2/29/2012 11:10:00 CST should be 2/29/2012 7:10:00 HST
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "hst");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(7, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);


            //check empty timezoneshortname. Should return back the input unmodified
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, "");
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(11, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);

            //check null timezoneshortname. Should return back the input unmodified
            dt = _timezoneComputation.ConvertDateTimeToLocalDateTime(cstTime, null);
            Assert.AreEqual(cstTime.Date, dt.Date);
            Assert.AreEqual(11, dt.Hour);
            Assert.AreEqual(10, dt.Minute);
            Assert.AreEqual(0, dt.Second);
        }

        #endregion

    }


Make Custom Gifts at CafePress