Creating a simple, reusable Windows Service (template code) - Part II
Part II
In part I, we examined how we can extend the Service Control methods by adding methods for all states of the Windows service in addition to the two methods provided by the designer. As you may remember, each of those methods called our own private Service Control method. Our private Service Control methods are implemented below:
Implementing our own Service Control methods
#Region " Implementation of Service Related Methods "
'/// <summary>
'/// Service Start.
'/// </summary>
Private Sub ServiceStart()
'Create event log
If Me.s_logMain Is Nothing Then
Me.s_logMain = New EventLog("APPLICATION", ".", Me.m_strServiceName)
End If
'If the status is PAUSE
If Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_PAUSE Then
'Resume main thread
Me.s_thrdMaster.Resume()
'Set Status
Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_RUN
Else
'Set Status
Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_RUN
'Start Main Processing Thread
Me.s_thrdMaster.Start()
End If
End Sub
Our ServiceStart() method does exactly what it says: It starts the service. Remember that this method is called by the OnStart() overriden Service method, so it does what it does whenever OnStart() runs. That is, whenever the service is started or resumed from Pause.
The first thing this method does is to write something to the Event Log so that we know that the service has started. Then, it checks the status using the s_enumMode variable (mdlGlobals.ServiceModeEnum). Essentially, the variable at this point can have the value MODE_PAUSE or the value MODE_STOP or the value MODE_SHUTDOWN. If the value of the variable is MODE_PAUSE, then we can assume that the service is entering run mode from a pause state (which means that our main thread of execution has been paused, as you'll see below) and so we resume the main thread. In any other case, we can safely assume that our main thread has not been started so we just start it. In both cases, what we must do afterwards is to set the variable to MODE_RUN to indicate that our service is running.
Let's see now how we should implement the ServiceStop() method, called by the OnStop() overriden service method:
'/// <summary>
'/// Service Stop.
'/// </summary>
Private Sub ServiceStop()
'Terminate Master thread
If Me.s_thrdMaster.IsAlive = True Then
'If the thread is suspended, resume it before aborting
If Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_PAUSE Then
Try
Me.s_thrdMaster.Resume()
Catch
End Try
End If
'Abort Master thread
Me.s_thrdMaster.Abort()
'This is needed in case we have other threads, but no harm done if we leave it
Me.s_thrdMaster.Join()
End If
'Set Status
Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_STOP
End Sub
Here we check if we have a "live" main thread. If so, we must abort it. Because we don't really know what the thread's status is, we must check whether the thread is paused or active. We can do so by checking the value of the s_enumMode variable. If the value is MODE_PAUSE, then we can safely assume that our thread has been paused (as you'll see below), and what we must do is to resume it and kill it immediately afterwards. (Not a very good way, I'm open to better ones). In all other cases, we don't need to do something else. After this check, we just abort the main thread and (this is optional) wait for any other threads to finish. When it's all done, we set the value of the s_enumMode variable to MODE_STOP so that our methods will know the service's current status.
Let's see the remaining methods now:
'/// <summary>
'/// Service Restore
'/// </summary>
Private Sub ServiceContinue()
'Resume Master thread
If Me.s_thrdMaster.IsAlive = True Then
Me.s_thrdMaster.Resume()
End If
'Set Operational Mode
Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_RUN
End Sub
'/// <summary>
'/// Service Pause
'/// </summary>
Private Sub ServicePause()
Try
'Pause Master thread
If Me.s_thrdMaster.IsAlive = True Then
Me.s_thrdMaster.Suspend()
End If
'Set Operational Mode
Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_PAUSE
Catch ex As Exception
'TODO: Error handling
End Try
End Sub
'/// <summary>
'/// Service Shutdown
'/// </summary>
Private Sub ServiceShutdown()
'Set Operational Mode
Me.s_enumMode = mdlGlobals.ServiceModeEnum.MODE_SHUTDOWN
'Do some sleep
Thread.CurrentThread.Sleep(1000)
'Terminate Master thread (in any case)
If Me.s_thrdMaster.IsAlive = True Then
Me.s_thrdMaster.Abort()
Me.s_thrdMaster.Join()
End If
End Sub
#End Region
As you can see, we follow the same idea with the other methods, i.e. handling the main thread and setting the correct value to the control variable. An interesting note is that we pause for a second when we shut down the service to give time to the main thread to finish what it's doing. The period of time we should pause depends on what the main thread is actually doing. Remember, the s_enumMode variable WILL be visible from our main thread, so the thread can possibly check the value of the variable and decide what it should do itself. In this example, the service control methods handle the status of the main thread, but that can be possibly handled by another thread if we extend the code, leaving to the control methods only the responsibility to set the appropriate value to the s_enumMode variable. (Which is more correct, by the way, because in the current implemenation our methods will brutally abort/pause/stop the thread when they need to, something which can prove risky in certain situations).
Conclusion
Having done all of the above, we have created a simple Windows service with a main thread which can be paused and aborted when the service is paused and stopped / shut down. Not much of a safe logic here, but remember that this is only an example. What you can do from now on is to create your own assembly, and hook its entry point to your main thread which will be repeating itself once in a while. (See the code for the main thread and assign your own period of time). Of course you can create a more complex (and safer) logic for controlling the thread and I will be more than happy to see your own examples.