ORDINA BLOGT

Building Windows Services with C# and Topshelf

The quickstart on the Topshelf site is only showing the real basics of getting a Windows Service up and running.

  • 6 maart 2013

quickstart

There are a couple of importants details not touched, that I will write about in this blog.

Create a new Solution with the Console Application Template. Because Topshelf is doing the plumbing, we don't need to use the Windows Service template anymore.

002

 
If you're using Visual Studio 2010, don't forget to turn off Client Profile

When the solution is created it will look like this:

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 
namespace XmplWindowsService
 {
     class Program
     {
         static void Main(string[] args)
         {
         }
     }
 }
 

The Main method is the entrypoint of the Windows Service. This is where we will setup Topshelf. But before we do that, we have to create a class that will do the actual work that will be done inside the Windows Service. So let's create a new class:
 
public class MyService
 
{
 
}
 

This class must have a Start() and a Stop() method:
 

public class MyService
 
{
 
   public void Start()
 
   {
 
   }

   public void Stop()
 
   {
 
   }
 }
 

This is what we have so far:

 
using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 

namespace XmplWindowsService
 
{
     class Program
     {
         static void Main(string[] args)
         {
         }
    }

           public class MyService
     {
         public void Start()
         {
         }
         public void Stop()
         {
         }
     }
 }
 

Now we can install Topshelf into the project:

004

 

Let's setup Topshelf inside the Main method of the Console Application: 

static void Main(string[] args)
 {
    HostFactory.Run(hostConfigurator =>
    {
       hostConfigurator.Service<MyService>(serviceConfigurator =>
       {
          serviceConfigurator.ConstructUsing(() => new MyService());
          serviceConfigurator.WhenStarted(myService => myService.Start());
          serviceConfigurator.WhenStopped(myService => myService.Stop());
       });
 
      hostConfigurator.RunAsLocalSystem();
 
      hostConfigurator.SetDisplayName("MyService");
       hostConfigurator.SetDescription("MyService using Topshelf");
       hostConfigurator.SetServiceName("MyService");
    });
 }
 

When we run the application, we will see this: 

Configuration Result:
 [Success] Name MyService
 [Success] Description MyService using Topshelf
 [Success] ServiceName MyService
Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
The MyService service is now running, press Control+C to exit.

It shows us its name, description and servicename, just like we configured. We can see the version numbers, and the last line tells us how the stop the Windows Service, when debugging or when it's running as console application. Offcourse we can't press Control+C to stop it when it's running as a Windows Service.

Now the MyService class isn't doing anything just yet. So now it's time to give MyService some work to do. Let's for example write something to screen every second:

public class MyService
 {
    public void Start()
    {
       while (true)
       {
          Console.WriteLine("I am working");
          System.Threading.Thread.Sleep(1000);
       }
    }
 
   public void Stop()
    {
    }
 }

When we start the app we will see it write "I am working" to the screen every second. However, when we press Control+C the application will not stop. Topshelf will try to stop MyService, but won't succeed since MyService is in a never ending while-loop.

So we have to have a signal to let the loop know when to stop. Therefore we add a boolean named doWork, and set it to true when we start the loop. The doWork boolean will be set to false when we press Control+C and Topshelf calls the Stop() method.

public class MyService
 {
    private bool _doWork;
 
   public void Start()
    {
       _doWork = true;
 
      while (_doWork)
       {
          Console.WriteLine("I am working");
          System.Threading.Thread.Sleep(1000);
       }
    }
 
   public void Stop()
    {
       _doWork = false;
    }
 }

When we start the debugger and press Control+C, the application will quit as it should.

Unfortunately the code is still nog perfect. Remember when we first started the application where MyService did nothing and Topshelf wrote this to the console:

Configuration Result:
[Success] Name MyService
[Success] Description MyService using Topshelf
[Success] ServiceName MyService
Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
The MyService service is now running, press Control+C to exit.

Now that MyService is running in a while loop, the last line is not printed anymore. Well actually it is printed after we stopped the service.

Configuration Result:
 [Success] Name MyService
 [Success] Description MyService using Topshelf
 [Success] ServiceName MyService
 Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
 I am working
 I am working
 I am working
 Control+C detected, attempting to stop service.
 The MyService service is now running, press Control+C to exit.
 The MyService service has stopped.
 
The message that the service is running, is printed after the Start() method of MyService returns, but that only happens when we stop the service. If we would use a Timer to do something every second it wouldn't be a problem, because a Timer is doing it's work asynchronously. And when we read messages from a queue async it wouldn't be a problem either. But I think it's good to show how we can make the Start() method in this example return immediately too.

System.Threading.Tasks

We are going to use the System.Threading.Tasks namespace to make MyService execute asynchronous work.
 
First let's move the content of the Start() method, to another method called DoWork():
 
private void DoWork()
 
{
    _doWork = true;
 
   while (_doWork)
    {
       Console.WriteLine("I am working");
       System.Threading.Thread.Sleep(1000);
    }
 }

we are going to call the DoWork() method with a Task. Let's create this task in the constructor of MyService:
 

public class MyService
 {
    bool _doWork;
    readonly Task _task;
 
   public MyService()
    {
       _task = new Task(DoWork);
    }
 
   ...
 }

Now we can start this Task from the Start() method of MyService:

public void Start()
 {
    _task.Start();
 }
 

This is what MyService looks like now:

 public class MyService
 {
    bool _doWork;
    readonly Task _task;
 
   public MyService()
    {
       _task = new Task(DoWork);
    }
 
   public void Start()
    {
       _task.Start();
    }
 
   public void Stop()
    {
       _doWork = false;
    }
 
   private void DoWork()
    {
       _doWork = true;
 
      while (_doWork)
       {
          Console.WriteLine("I am working");
          System.Threading.Thread.Sleep(1000);
       }
    }
 }
 

When we run the application, we will see this: 

Configuration Result:
 [Success] Name MyService
 [Success] Description MyService using Topshelf
 [Success] ServiceName MyService
 Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
 The MyService service is now running, press Control+C to exit.
 I am working
 I am working
 
Exactly what we want. The message that MyService is running is coming first, and then the "I am working" messages are written.
 
But it's still not completely right. Let me tell you why. 

Quit before work is finished

The problem that we have now is that the Windows Service will quit if you press Control+C, but it doesn't take into account any work in progress. This can be demoed quite easily.
 
Add some more work to the DoWork() method:

private void DoWork()
 {
    _doWork = true;
 
   while (_doWork)
    {
      Console.WriteLine("I am working");
 
      Console.WriteLine("   Step 1");
      System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 2");
      System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 3");
      System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 4");
       System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 5");
       System.Threading.Thread.Sleep(1000);
    }
 }
 
If we run the application, and then press Control+C after step one, we can get output like this:
 
Configuration Result:
 
[Success] Name MyService
 [Success] Description MyService using Topshelf
 [Success] ServiceName MyService
 Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
 

The MyService service is now running, press Control+C to exit.
 I am working
   Step 1
   Step 2
   Step 3
   Step 4
   Step 5
 I am working
   Step 1
 Control+C detected, attempting to stop service.
 The MyService service has stopped.

The yellow marked lines are important. You can see that step 2, 3, 4 and 5 are not written, because the Service has stopped. However, I only want the service to be stopped when step 5 is finished. The best way to do this is remove the _doWork boolean, and use a Cancellationtoken.
 
Let's switch back to the constructor of MyService, and create a CancellationToken that we can use in the Task:
 
public class MyService
 {
    readonly CancellationTokenSource _cancellationTokenSource;
    readonly CancellationToken _cancellationToken;
    readonly Task _task;
 
   public MyService()
    {
       _cancellationTokenSource = new CancellationTokenSource();
       _cancellationToken = _cancellationTokenSource.Token;
 
      _task = new Task(DoWork, _cancellationToken);
    }
 
   ...
 }
 

Now we can use the CancellationTokenSource in the DoWork() method:
 
private void DoWork()
 
{
 
   while (!_cancellationTokenSource.IsCancellationRequested)
 
   {
 
      Console.WriteLine("I am working");
 
      Console.WriteLine("   Step 1");
       System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 2");
       System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 3");
       System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 4");
       System.Threading.Thread.Sleep(1000);
 
      Console.WriteLine("   Step 5");
       System.Threading.Thread.Sleep(1000);
 
   }
 
}
 
And we can cancel the Task in the Stop() method:

public void Stop()
 
{
 
   _cancellationTokenSource.Cancel();
 
   _task.Wait();
 
}
 

If we run the application, and then press Control+C after step one, we get exactly what we want:
 

Configuration Result:
 
[Success] Name MyService
 
[Success] Description MyService using Topshelf
 
[Success] ServiceName MyService
 
Topshelf v3.1.106.0, .NET Framework v4.0.30319.18033
 
The MyService service is now running, press Control+C to exit.
 

I am working
 
  Step 1
 
  Step 2
 
  Step 3
 
  Step 4
 
  Step 5
 
I am working
 
  Step 1
 
Control+C detected, attempting to stop service.
 
  Step 2
 
  Step 3
 
  Step 4
 
  Step 5
 
The MyService service has stopped.
 

Conclusion

In this blog I've shown how easy it is to create a Windows Service in C# and the Topshelf nuget package. I've also shown how you can use a Task to start an asynchronous proces to do some work, and how the task can be stopped in a clean way, without leaving partial executed work behind.