@bobnoordam

Creating a threaded windows service

Below is a more or less empty sample from a window service project, you can build in Visual Studio.

The service itsself (created by the VS Project) has events for Start and Stop command, which you use to hook your worker code into:

using System.ServiceProcess;

namespace PerformanceCollectorService
{
    public partial class PerformanceCollectorService : ServiceBase
    {
        private CollectorTask _collectorTask;

        public PerformanceCollectorService() {
            InitializeComponent();
        }

        /// <summary>
        ///     Service start initializes a new worker, and issues a start command
        /// </summary>
        /// <param name="args"></param>
        protected override void OnStart(string[] args) {
            _collectorTask = new CollectorTask();
            _collectorTask.Start();
        }

        /// <summary>
        ///     Service stop issues a stop command to the existing worker
        /// </summary>
        protected override void OnStop() {
            _collectorTask.Stop();
        }
    }
}

Example of an empty worker, which does not do more then writing event to the eventlog

using System.Diagnostics;
using System.Threading;

namespace PerformanceCollectorService
{
    internal class CollectorTask
    {
        private Timer _timer;

        /// <summary>
        ///     Starting the worker initializes a timer with a specified delay and interval
        /// </summary>
        public void Start() {
            EventLog.WriteEntry("PerformanceCollectorService", "Service Start", EventLogEntryType.Information, 1000);
            _timer = new Timer(Callback, null, 6000, 6000);
        }

        public void Stop() {
            EventLog.WriteEntry("PerformanceCollectorService", "Service Stop", EventLogEntryType.Information, 1002);
            _timer.Change(-1, -1);
        }

        private void Callback(object state) {
            EventLog.WriteEntry("PerformanceCollectorService", "Service Callback", EventLogEntryType.Information, 1001);
        }

    }
}

For installation, Advanced Installer is a good option, that offers the option to build either MSI or MSIX packages around a service.

Below is the original Visual Basic document from 2008, Please do not use it, it is just left here for reference from another era

Creating the project and installation procedures

Start Visual Studio, and choose the windows service project type. And empty objects panel appears. Drag any controls you will use onto this panel. Many examples use a timer control, but i choose a different concept for two reasons;

  • Timer controls can be problematic in windows services. The default timer control of a forms application is bugged, and will not – or not always fire. Usage of a system.threading timer is another option that works, but is less intuitive for an example.
  • You may not want to work with a timed event, and use a continuously running background programm.

This example uses such a model. It uses a global variable to indicate wheter the main loop is running, and another one to indicate if a stop request has been received from the service manager. Using these two variables demonstrates how to communicate between the events and the main programm that has been fired off by the service start into a seperate thread.

Rightclick the panel, and choose “Add Installer”, This will add all the installer objects needed.

On the serviceeprojectinstaller1 object; set the account to Localsystem

Adding the code

By double clicking on the service design panel, you will be able to access the actual code for the service. The complete code is below:

Public Class Service1
  
Dim v_shutdownsignal As Boolean = False
Dim v_mainlooprunning As Boolean = False
  
Protected Overrides Sub OnStart(ByVal args() As String)
' Add code here to start your service. This method should set things
' in motion so your service can do its work.
  
WriteEvent("WinServicedemo v1.0.1 starting", "WinServiceDemo", EventLogEntryType.Information, "WinServiceDemo")
  
' --- Fire the main loop of our programm
Dim th As New Threading.Thread(AddressOf MainLoop)
th.IsBackground = True
th.Start()
  
End Sub
  
Protected Overrides Sub OnStop()
' Add code here to perform any tear-down necessary to stop your service.
  
' --- Signal the main loop to stop
v_shutdownsignal = True
  
' --- Wait for the main loop to signal that is has stopped
While v_mainlooprunning = True
  
' --- Wait for the main loop to die
System.Threading.Thread.Sleep(10)
WriteEvent("Waiting for the main loop to stop...", "WinServiceDemo", EventLogEntryType.Information, "WinServiceDemo")
  
End While
  
' --- Now the mainloop has shut down cleanly
WriteEvent("WinServicedemo stopping", "WinServiceDemo", EventLogEntryType.Information, "WinServiceDemo")
  
End Sub
  
Public Sub MainLoop()
  
' --- This is the main loop of our service
  
' --- Set the global to indicate the stop controller that we are running
v_mainlooprunning = True
  
' --- Run the main programm until we get a signal that the user stopped the service
While v_shutdownsignal = False
  
WriteEvent("The minute event fired in the main loop", "WinServiceDemo", EventLogEntryType.Information, "WinServiceDemo")
Dim n_delay As Integer
While n_delay < 60 And v_shutdownsignal = False ' wait 60 seconds, OR till a shutdown request
n_delay = n_delay + 1
System.Threading.Thread.Sleep(1000)
End While
  
End While
  
' --- Signal that we are done
v_mainlooprunning = False
  
End Sub
  
Public Function WriteEvent(ByVal sEntry As String, ByVal sAppName As String, ByVal eEventType As EventLogEntryType, ByVal sLogName As String) As Boolean
  
' --- Write to the eventlog
' Source: http://www.dreamincode.net/code/snippet1122.htm
' If a non existing logfile is specified, it will be created for us
  
Dim oEventLog As New EventLog
Try
'Register the Application as an Event Source
If Not EventLog.SourceExists(sAppName) Then
EventLog.CreateEventSource(sAppName, sLogName)
End If
  
'log the entry
oEventLog.Source = sAppName
oEventLog.WriteEntry(sEntry, eEventType)
Return True
Catch Ex As Exception
Return False
End Try
End Function
  
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs)
  
' --- Write an event every tick to the eventlog
WriteEvent("WinServicedemo timer event", "WinServiceDemo", EventLogEntryType.Information, "WinServiceDemo")
  
End Sub
End Class

Installing the service

BUILD the project. This will result in the binary being present in the project directory. Move the sercvice executable if you want to another directory where it will be run. Installation of the service is done with the installutil tool. This tool is directly accessible from a Visual Sutdio command prompt, or from the .NET framework directory in your system directory (eg:)

Map van C:\windows\Microsoft.NET\Framework\v2.0.50727

27-07-2008 20:03 28.672 InstallUtil.exe 1 bestand(en) 28.672 bytes

run installutil winservicedemo.exe to install the service -or- run installutil /u winservicedemo.exe to remove the installed service2

Finaly, you should be able to see the service in the control panel’s service overview, and see the logged events.

Keep in mind that while running and installing your service from the project directory is fine during development, you will need to stop the service to be able to build (the executable will be in use after all). You may want to change the name of the base class of the project (service1 in this example), as that is the name that will be used in the application log by windows to log events about your service.

C# Code example

using System;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
  
namespace PopzGuardian
{
public partial class PopzGuardian : ServiceBase
{
  
private bool vStopCmd; // will be used to pass a stop cmd to the background service
private bool vIsRunning; // Will be used to pass stop/run status back from the background service
  
public PopzGuardian()
{
InitializeComponent();
}
  
// OnStart is executed when the service is started, and launches the background thread
protected override void OnStart(string[] args)
{
EventLog.WriteEntry("PopzGuardian starting");
vStopCmd = false;
Thread th = new Thread(MainLoop);
th.IsBackground = true;
th.Start();
}
  
// OnStop is executed when the service is stopped. It passes the stopcommand on to the back
// ground service, and waits for it to change it's status from running to stopped.
protected override void OnStop()
{
vStopCmd = true;
EventLog.WriteEntry("PopzGuardian is now stopping.");
while (vIsRunning == true)
{
Thread.Sleep(10);
}
}
  
// This is the main worker thread of the service.
protected void MainLoop()
{
vIsRunning = true;
EventLog.WriteEntry("PopzGuardian is now running");
while (vStopCmd == false)
{
Thread.Sleep(100);
// Work goes here
}
EventLog.WriteEntry("PopzGuardian stopped.");
vIsRunning = false;
}
  
}
}

Install projects

If you have created a setup project to install your service you need to make a slight adjustment to tell it that you actuably want to install a service. Rightclick the project, choose view, and then “custom actions”

Rightclick the custom actions node and “add custom action”

Choose your project output for the custom action

Your project output is now listed in each of the steps

Build the setup project and deploy, the service will now be installed by the installer application.