Blog

State-Case Machines Replace Gotos

State-Case Machines Replace Gotos

Recently, I created a function to execute this simple flowchart:

State Case Machine Flowchart

This function is executing within its own thread and starts an asynchronous action that executes in another thread. This function periodically checks the system state. Before the asynchronous action is started, an initialization action must occur. After it is started, if the system state changes to Pause, the asynchronous action must be halted and reinitialized before it is restarted.

Initially, my first attempt at implementing this flowchart used nested while loops and carefully placed continue and break statements. The extensive nesting makes this approach complicated and very inflexible. There is also some redundant code. If the flowchart became any more complicated, this approach would become unworkable. Here's an example implementation (examples are written in C# with the .NET Framework 3.5):

private bool NestedWhileLoops()
{
	while ( true )
	{
		switch ( CheckSystemState() )
		{
		case SystemState.Stop:
			return false;
		case SystemState.Pause:
			Thread.Sleep( 500 );
			continue;
		case SystemState.Run:
			break;
		}

		if ( InitializeAsyncAction() == false )
		{
			return false;
		}

		switch ( CheckSystemState() )
		{
		case SystemState.Stop:
			return false;
		case SystemState.Pause:
			Thread.Sleep( 500 );
			continue;
		case SystemState.Run:
			break;
		}

		if ( StartAsyncAction() == false )
		{
			return false;
		}

		while ( true )
		{
			switch ( CheckAsyncAction() )
			{
			case AsyncActionStatus.Error:
				KillAsyncAction();
				return false;

			case AsyncActionStatus.StillExecuting:
				switch ( CheckSystemState() )
				{
				case SystemState.Stop:
					KillAsyncAction();
					return false;

				case SystemState.Pause:
					if ( HaltAsyncAction() == false )
					{
						KillAsyncAction();
						return false;
					}
					break;

				case SystemState.Run:
					Thread.Sleep( 500 );
					continue;
				}

				// System state is pause and async action was halted.
				break;

			case AsyncActionStatus.Finished:
				return true;
			}

			// System state is pause and async action was halted.
			break;
		}
	}
}

My second attempt at implementing this flowchart tried to reduce the level of nesting by using goto statements. This approach had serious consequences... xkcd.com/292/. Here's the example implementation:

private bool GotoStatements()
{
InitializeAsync:
	switch ( CheckSystemState() )
	{
	case SystemState.Stop:
		return false;
	case SystemState.Pause:
		Thread.Sleep( 500 );
		goto InitializeAsync;
	case SystemState.Run:
		break;
	}

	if ( InitializeAsyncAction() == false )
	{
		return false;
	}

	switch ( CheckSystemState() )
	{
	case SystemState.Stop:
		return false;
	case SystemState.Pause:
		Thread.Sleep( 500 );
		goto InitializeAsync;
	case SystemState.Run:
		break;
	}

	if ( StartAsyncAction() == false )
	{
		return false;
	}

CheckAsync:
	switch ( CheckAsyncAction() )
	{
	case AsyncActionStatus.Error:
		goto KillAsync;

	case AsyncActionStatus.StillExecuting:
		switch ( CheckSystemState() )
		{
		case SystemState.Stop:
			goto KillAsync;

		case SystemState.Pause:
			if ( HaltAsyncAction() == false )
			{
				goto KillAsync;
			}
			
			goto InitializeAsync;

		case SystemState.Run:
			Thread.Sleep( 500 );
			goto CheckAsync;
		}

	case AsyncActionStatus.Finished:
		return true;
	}

KillAsync:
	KillAsyncAction();
	return false;
}

Both of the above implementations are complicated and hard to maintain. A more systematic approach involves what I call the state-case machine. This approach is similar to the scan of a PLC with its overall while statement. Then, a switch-case machine handles the logic for each state and the state transitions. States are an enumerated type. Here's an example implementation:

private enum State
{
	PreInitCheckState,
	InitializeAsync,
	PreStartCheckState,
	StartAsync,
	CheckAsync,
	PostCheckAsyncCheckState,
	HaltAsync,
	KillAsync
}

private bool StateCaseMachine()
{
	State enmState = State.PreInitCheckState;

	while ( true )
	{
		switch ( enmState )
		{
		case State.PreInitCheckState:
			switch ( CheckSystemState() )
			{
			case SystemState.Stop:
				return false;
			case SystemState.Pause:
				Thread.Sleep( 500 );
				break;
			case SystemState.Run:
				enmState = State.InitializeAsync;
				break;
			}
			break;

		case State.InitializeAsync:
			if ( InitializeAsyncAction() == false )
			{
				return false;
			}

			enmState = State.PreStartCheckState;
			break;

		case State.PreStartCheckState:
			switch ( CheckSystemState() )
			{
			case SystemState.Stop:
				return false;
			case SystemState.Pause:
				Thread.Sleep( 500 );
				enmState = State.PreInitCheckState;
				break;
			case SystemState.Run:
				enmState = State.StartAsync;
				break;
			}
			break;

		case State.StartAsync:
			if ( StartAsyncAction() == false )
			{
				return false;
			}

			enmState = State.CheckAsync;
			break;

		case State.CheckAsync:
			switch ( CheckAsyncAction() )
			{
			case AsyncActionStatus.Error:
				enmState = State.KillAsync;
				break;

			case AsyncActionStatus.StillExecuting:
				enmState = State.PostCheckAsyncCheckState;
				break;

			case AsyncActionStatus.Finished:
				return true;
			}
			break;

		case State.PostCheckAsyncCheckState:
			switch ( CheckSystemState() )
			{
			case SystemState.Stop:
				enmState = State.KillAsync;
				break;

			case SystemState.Pause:
				enmState = State.HaltAsync;
				break;

			case SystemState.Run:
				Thread.Sleep( 500 );
				enmState = State.CheckAsync;
				break;
			}
			break;

		case State.HaltAsync:
			if ( HaltAsyncAction() == false )
			{
				enmState = State.KillAsync;
			}

			enmState = State.PreInitCheckState;
			break;

		case State.KillAsync:
			KillAsyncAction();
			return false;
		}
	}
}

Once again, this approach is very systematic and inserting additional states involves only adding another item to the enumeration, adding a case to the switch-case, and adjusting the state transitions. The same pattern could be used for very simple or very complex flowcharts. This approach is also very easy to debug by watching the state enumeration and the relevant transition conditions.

And, you can be proud of your program instead of being ridiculed for writing spaghetti code.

Comments

There are currently no comments, be the first to post one.

Post a comment

Name (required)

Email (required)

CAPTCHA image
Enter the code shown above: