I am developing a Windows Service application of some complexity, and need to have a way of gracefully transitioning between states. Since each state might need initialization / cleaning up, there must be a mechanism that coordinates the transitions between them and makes sure only valid states can be reached from any given state. The states in question are the familiar states of a Windows Service:
Stopped|Running|Paused|StopPending|StartPending|PausePending|ContinuePending
I have developed a system that seems to do this properly, but I am curious whether it has flaws that I just haven’t stumbled over yet and/or if there are more tried-and-tested patterns / best practices that I could or should use instead.
This is implemented in a Bootstrapper class that exposes methods corresponding to the commands I should expect from a Windows Service: Start, Stop, Pause, Continue (so that the Service’s OnStart command will simply call the class’ Start method, OnStop will call Stop etc):
In the Service code (this is VB.Net, but I had to use // for comments here, since ‘ seemed to mess things up in the code listing):
Sub OnStart()
Bootstrapper.Start()
End Sub
In the Start() method of my class (I bypass threading and exception handling and other logic for simplicity here):
Public Sub Start()
If RequestState(State.Running) = True Then
// Log success
Else
// Log failure
End If
End Sub
Then, inside my class, there is a similar set of private OnStart(), OnStop(), OnPause() and OnContinue() methods that perform the actual initialization / cleanup for each state:
Private Function OnStart() As Boolean
SetState(State.StartPending)
// Do something
Return SetState(State.Running)
End Function
As you can see above, there are calls being made to two other methods – RequestState() and SetState(). That’s where the logic actually takes place, and it works as follows:
A command is being sent to the application (Start, Stop, Pause or Continue). The receiving method calls RequestState() with the desired end state passed as a parameter. If that state was reachable it returns True, otherwise False.
RequestState() will use Select constructs to determine the right action, based on the current state and the requested state.
SetState() actually sets the state of the application, and after doing so, checks whether there is some ‘next’ state ‘queued up’ (this is so that a StartPending process will be able to finish and end up in a Running state, before a Pause command is issued).
Forgive me if I introduce these methods in a jumbled order, but from where I sit, it seemed like the right way to introduce the functionality. Here are the RequestState() and SetState() methods (sorry for the sheer length, but I felt it was needed for completeness):
Private _currentState As State
Private _nextState As State
Private Function RequestState(ByVal requestedState As State) As Boolean
Select Case requestedState
Case State.StartPending, State.StopPending, State.PausePending, State.ContinuePending, State.Exception, State.None
Throw New ArgumentException(requestedState.ToString & " cannot be requested directly.")
Case Else
_nextState = requestedState
Select Case _currentState
Case State.Exception
Return False
Case State.Stopped
Select Case requestedState
Case State.Stopped
Return True
Case State.Running
Return OnStart()
Case Else
Return False
End Select
Case State.Running
Select Case requestedState
Case State.Stopped
Return OnStop()
Case State.Running
Return True
Case State.Paused
Return OnPause()
Case Else
Return False
End Select
Case State.Paused
Select Case requestedState
Case State.Stopped
Return OnStop()
Case State.Running
Return OnContinue()
Case State.Paused
Return True
Case Else
Return False
End Select
Case State.StartPending
Select Case requestedState
Case State.Stopped
_nextState = State.Stopped
Return True
Case State.Running
Return True
Case State.Paused
_nextState = State.Paused
Return True
Case Else
Return False
End Select
Case State.StopPending
Select Case requestedState
Case State.Stopped
Return True
Case State.Running
_nextState = State.Running
Return True
Case Else
Return False
End Select
Case State.ContinuePending
Select Case requestedState
Case State.Stopped
_nextState = State.Stopped
Return True
Case State.Running
Return True
Case State.Paused
_nextState = State.Paused
Return True
Case Else
Return False
End Select
Case State.PausePending
Select Case requestedState
Case State.Stopped
_nextState = State.Stopped
Return True
Case State.Running
_nextState = State.Running
Return True
Case State.Paused
Return True
Case Else
Return False
End Select
Case Else
Return False
End Select
End Select
End Function
Private Function SetState(ByVal newState As State) As Boolean
_currentState = newState
If newState = State.Running OrElse newState = State.Stopped OrElse newState = State.Paused Then
If _currentState = _nextState Then
Return True
End If
Return RequestState(_nextState)
Else
Return True
End If
End Function
I’d say its ok as-is. There aren’t many states and its reasonably easy to follow. I’d say there are some places where you can make the code a bit shorter, but otherwise its not too bad.
However, it might be easier creating a real state table. Start by mapping your state table out in a grid like this:
Now to convert this into code, consider that this is basically a
Dictionary<State, Dictionary<State, Func<bool>>>, where the outer key is your StartState, outer Value is your transition table.This code is not tested, but should give you a general idea of where to start (using C# since my VB-fu is rusty):