title: Windows Service with Parallel Task - stopping service gracefully
body: <p>This is a follow-up from my previous question here: <a href="https://stackoverflow.com/questions/22674392/writing-a-sync-application-using-windows-service-to-process-files-in-parallel">Writing a sync application using Windows Service to process files in Parallel</a></p>
  
  <p>I've got the timing and processing correctly setup exactly as I want. When the service is installed and I start it, so it's doing some work and then stop it randomly, I had a windows service stop error and I found the following error in the Event Viewer:</p>
  
  <pre><code>Application: SyncTest.exe
  Framework Version: v4.0.30319
  Description: The process was terminated due to an unhandled exception.
  Exception Info: System.AggregateException
  Stack:
     at System.Threading.Tasks.Parallel.ForWorker[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](Int32, Int32, System.Threading.Tasks.ParallelOptions, System.Action`1&lt;Int32&gt;, System.Action`2&lt;Int32,System.Threading.Tasks.ParallelLoopState&gt;, System.Func`4&lt;Int32,System.Threading.Tasks.ParallelLoopState,System.__Canon,System.__Canon&gt;, System.Func`1&lt;System.__Canon&gt;, System.Action`1&lt;System.__Canon&gt;)
     at System.Threading.Tasks.Parallel.ForEachWorker[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Collections.Generic.IList`1&lt;System.__Canon&gt;, System.Threading.Tasks.ParallelOptions, System.Action`1&lt;System.__Canon&gt;, System.Action`2&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState&gt;, System.Action`3&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState,Int64&gt;, System.Func`4&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState,System.__Canon,System.__Canon&gt;, System.Func`5&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState,Int64,System.__Canon,System.__Canon&gt;, System.Func`1&lt;System.__Canon&gt;, System.Action`1&lt;System.__Canon&gt;)
     at System.Threading.Tasks.Parallel.ForEachWorker[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Collections.Generic.IEnumerable`1&lt;System.__Canon&gt;, System.Threading.Tasks.ParallelOptions, System.Action`1&lt;System.__Canon&gt;, System.Action`2&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState&gt;, System.Action`3&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState,Int64&gt;, System.Func`4&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState,System.__Canon,System.__Canon&gt;, System.Func`5&lt;System.__Canon,System.Threading.Tasks.ParallelLoopState,Int64,System.__Canon,System.__Canon&gt;, System.Func`1&lt;System.__Canon&gt;, System.Action`1&lt;System.__Canon&gt;)
     at System.Threading.Tasks.Parallel.ForEach[[System.__Canon, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]](System.Collections.Generic.IEnumerable`1&lt;System.__Canon&gt;, System.Action`1&lt;System.__Canon&gt;)
     at SyncTest.StartProcess(System.Object)
     at System.Threading.TimerQueueTimer.CallCallbackInContext(System.Object)
     at System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
     at System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
     at System.Threading.TimerQueueTimer.CallCallback()
     at System.Threading.TimerQueueTimer.Fire()
     at System.Threading.TimerQueue.FireNextTimers()
     at System.Threading.TimerQueue.AppDomainTimerCallback()
  </code></pre>
  
  <p>This is my code so far:</p>
  
  <pre><code>public partial class Service : ServiceBase
  {
      static Timer _SyncTimer;
      static Object _SyncLock = new Object();
  
      public Service()
      {
          InitializeComponent();
  
          // Init
          ConsoleLog("--- Sync Initializing ---");
      }
  
      protected override void OnStart(string[] args)
      {
          // Every 5 Seconds
          _SyncTimer = new Timer(StartProcess, null, 0, 5000);
      }
  
      protected override void OnStop()
      {
          // Stop Timer
          if (null != _SyncTimer)
          {
              ConsoleLog("### Stopping Process ###");
              try
              {
                  _SyncTimer.Dispose();
              }
              catch { }
              finally
              {
                  _SyncTimer = null;
              }
          }
      }
  
      static void StartProcess(Object state)
      {
          ConsoleLog("### Starting Process ###");
          bool lockTaken = false;
          try
          {
              Monitor.TryEnter(_SyncLock, ref lockTaken);
              if (lockTaken)
              {
                  ConsoleLog("Lock Acquired. Doing some dummy work...");
  
                  List&lt;string&gt; fileList = new List&lt;string&gt;()
                  {
                      "fileA.csv",
                      "fileB.csv"
                  };
  
                  Parallel.ForEach(fileList, (string fileName) =&gt;
                  {
                      ConsoleLog("Processing File: " + fileName);
                      Thread.Sleep(10000); // 10 sec to process each file
                  });
  
                  GC.Collect();
              }
              else
                  ConsoleLog("Sync Is Busy, Skipping Cycle");
          }
          finally
          {
              if (lockTaken)
                  Monitor.Exit(_SyncLock);
          }
      }
  
      static void ConsoleLog(String Message)
      {
          using (StreamWriter sw = File.AppendText(@"C:\SyncTest\application.log"))
          {
              sw.WriteLine("[{0}]: {1}",
                  DateTime.Now.ToString("dd/mm/yyyy HH:mm:ss tt"),
                  Message);
          }
      }
  }
  </code></pre>
  
  <p>It does appear to start and stop from the windows services correctly:</p>
  
  <pre><code>[01/22/2014 08:22:58 AM]: --- Sync Initializing ---
  [01/22/2014 08:22:58 AM]: ### Starting Process ###
  [01/22/2014 08:22:58 AM]: Lock Acquired. Doing some dummy work...
  [01/22/2014 08:22:58 AM]: Processing File: fileA.csv
  [01/22/2014 08:22:58 AM]: Processing File: fileB.csv
  [01/23/2014 08:23:04 AM]: ### Starting Process ###
  [01/23/2014 08:23:04 AM]: Sync Is Busy, Skipping Cycle
  [01/23/2014 08:23:06 AM]: ### Stopping Process ###
  [01/23/2014 08:23:50 AM]: --- Sync Initializing ---
  [01/23/2014 08:23:50 AM]: ### Starting Process ###
  [01/23/2014 08:23:50 AM]: Lock Acquired. Doing some dummy work...
  [01/23/2014 08:23:50 AM]: Processing File: fileA.csv
  [01/23/2014 08:23:50 AM]: Processing File: fileB.csv
  [01/23/2014 08:23:55 AM]: ### Starting Process ###
  [01/23/2014 08:23:55 AM]: Sync Is Busy, Skipping Cycle
  [01/23/2014 08:23:58 AM]: ### Stopping Process ###
  </code></pre>
  
  <p>How can I stop the sync gracefully? I.e. allow the current <code>Parallel.Foreach</code> to finish before the sync stops the timer and stop the sync "cycles"?</p>
  
  <hr>
  
  <p><strong>Update</strong></p>
  
  <p>After doing some further reading, I read somewhere on SO that you can use the <code>lock object</code> and <code>Monitor.TryEnter</code> to wait for current process to finish before exiting like this:</p>
  
  <pre><code>protected override void OnStop()
  {
      ConsoleLog("### Stopping Process ###");
      bool lockTaken = false;
      try
      {
          Monitor.TryEnter(_SyncLock, ref lockTaken);
          if (lockTaken)
          {
              try
              {
                  _SyncTimer.Dispose();
              }
              catch { }
              finally
              {
                  _SyncTimer = null;
              }
              GC.Collect();
          }
          else
              ConsoleLog("Sync Is Busy, Waiting To Stop");
      }
      finally
      {
          if (lockTaken)
              Monitor.Exit(_SyncLock);
      }
  }
  </code></pre>
  
  <p>This appears to be working (I no longer see errors), but I am unsure if this is the correct way to go about this....</p>