Maintaining a Consistent Application State with TPL

Zlatko Michailov - MSFT

The aim of this post is to help developers writing applications in which operations may need to be performed and then later undone due to a subsequent failure. It shows a pattern for how to maintain such a consistent application state by utilizing functionality from the Task Parallel Library (TPL) in the .NET Framework 4.

For the purposes of this blog post, a program/routine is a state machine where each state is equally valid and important and should be handled correctly regardless of whether it belongs to a happy path or not. Specifically, a step that rolls back an incomplete state transition is equally important as a step that makes a forward state transition. A lot could be written about the advantages and disadvantages of exceptions vs. error codes, and such a discussion is not the goal of this post: for the purpose of this article, it is only worth mentioning that throwing and catching exceptions is focused on the happy path (it keeps the code clean and easy to comprehend), while returning and checking error codes is focused on detecting the places of the code (program states) where deviations from the happy path may occur.

In general, a routine that consists of n atomic forward steps, where each forward step may fail and then needs to be undone, could be modeled as the following state machine:


 

 

 

 

 

 

 

 

 

 

 

Assuming a step is atomic means we don’t have to undo that particular step if it fails. That is why the undo sequences in the diagram start with the last successful step.

Note: It is also assumed that undoing may not fail. If failures are possible to occur during rollback, then you should strongly consider using a real transactional resource manager like a database server.

Now that we’ve stated the problem, what is the best way to approach it? Obviously, there are solutions through either error checking or through exception handling, but both of those come at the expense of convoluting the code. Notice that the number of state transition paths is about three times the number of forward steps. Ideally, we are looking for a solution that has a clean happy path as well as clean undo paths without any if statements or separate Exception types for each forward step. In other words, we are looking for a way to describe a program as a state machine.

That’s exactly what the TPL does through tasks (as states) and continuations (as state transitions).

Let’s take a concrete application as an example and write some real code. Let’s say we have a system that manages the process of making a sandwich. In a terminal state our resources, bread and ham, are in the fridge. (For the sake of simplicity, we produce one sandwich at a time.) First, we take a slice of bread out of the fridge. Second, we take a slice of ham out of the fridge. Third, we assemble the bread and the ham into a sandwich. If an operation fails, we have to return whatever ingredients are currently on the line back to the fridge so they don’t rot:

And here is the code of our routine:

// HAPPY PATH

Task retrieveBread = Task.Factory.StartNew(RetrieveBreadFromFridge);

Task retrieveHam = retrieveBread.ContinueWith(_ => RetrieveHamFromFridge(),

                                       TaskContinuationOptions.OnlyOnRanToCompletion);

Task assembleSandwich = retrieveHam.ContinueWith(_ => AssembleSandwich(),

                                       TaskContinuationOptions.OnlyOnRanToCompletion);

 

 

// RESET STATE

TaskCompletionSource<bool> reset = new TaskCompletionSource<bool>();

assembleSandwich.ContinueWith(_ => reset.SetCanceled(),

                              TaskContinuationOptions.OnlyOnRanToCompletion);

 

 

// UNDO PATH

Task returnBread = new Task(ReturnBreadToFridge);

returnBread.ContinueWith(_ => reset.SetResult(true),

                         TaskContinuationOptions.OnlyOnRanToCompletion);

 

Task returnHam = new Task(ReturnHamToFridge);

returnHam.ContinueWith(_ => returnBread.Start(),

                       TaskContinuationOptions.OnlyOnRanToCompletion);

 

 

// HAPPY PATH -> UNDO PATH

//      On a failure in bread retrieval – signal “reset”.

retrieveBread.ContinueWith(_ => reset.SetResult(true),

                           TaskContinuationOptions.OnlyOnFaulted);

 

//      On a failure in ham retrieval – return bread

retrieveHam.ContinueWith(_ => returnBread.Start(),

                         TaskContinuationOptions.OnlyOnFaulted);

 

//      On a failure in sandwich assembly- return ham and bread

assembleSandwich.ContinueWith(_ => returnHam.Start(),

                              TaskContinuationOptions.OnlyOnFaulted);

 

 

// WAIT

// Log the execution of the HAPPY PATH tasks.

// Additionally, wait on the reset state to get signaled.

Task[] loggedTasks = new Task[]

                         {retrieveBread, retrieveHam, assembleSandwich, reset.Task};

Task.Factory.ContinueWhenAll(loggedTasks,LogErrors)

            .Wait();

As you can see, there are no if statements, nor are there any Exception types customized to provide information what step failed.

Finally, I added a logging task that effectively waits for all the state machine tasks to finish, and then traverses the given task array and logs any error messages. (Notice that I still have to wait for that task to finish.) The body of that method is straightforward:

private void LogErrors(Task[] tasks)

{

    Console.WriteLine(“\n\tERRORS:”);

    foreach (Task task in tasks)

    {

        if (task.IsFaulted)

        {

            // Use the InnerException since it is wrapped in an AggregateException

            Console.WriteLine(“\t{0}”, task.Exception.InnerException.Message);

        }

    }

}

In conclusion, TPL enables writing state-sensitive applications on the .NET platform without convoluting the application code with many if statements or customized exception types. While the code is still a little verbose, it closely resembles the state machine’s diagram. The attached zip file contains the complete C# project. Feel free to download it and play with it. Your feedback is welcome.

ConsistentState.zip

0 comments

Discussion is closed.

Feedback usabilla icon