Introduction
The article shows how to emulate web continuation in .NET framework. It makes
the creation of complex flow in web applications possible.
Background
A continuation is a
representation of the execution state of a program at a certain point. Not all
languages can support continuations. Saving of the state in web application
gives additional difficulty.
Iterator block was introduced in C# 2.0. It can be presented as a method body
that returns one of the generic enumerator (enumerable) interfaces and contains
at least one yield statement. Following example shows how iterator block is
working. The function saves all internal variables and returns control to caller
on yield return
statement; the function restores state and
resumes execution when another value is requested.
static IEnumerable<int> Fibonacci(int n)
{
int previous = 0;
int current = 1;
for (int i = 0; i < n; i++)
{
yield return current;
int next = previous + current;
previous = current;
current = next;
}
}
...
foreach (int i in Fibonacci(10)) Console.WriteLine(i);
The enumerator saves local variables and execution resume point in memory,
but it doesn’t allow exporting and importing of the state to and from an
external stream. It’s the reason why it cannot be used as true web continuation
solution.
Using the code
Web continuation manager implemented as ContinuationManager
server control for .NET framework
(v2.0.50727). The ASPX page that uses continuation has to contain this
control.
As example, we will implement number guessing game using natural execution
flow. Let’s declare iterator block inside page code behind class:
private IEnumerator<object> NumberGuess()
{
Random random = new Random();
int answer = random.Next(1, 101);
int attempts = 1;
mainMultiView.SetActiveView(guessView);
lastGuessPanel.Visible = false;
yield return null;
while(guess != answer)
{
++attempts;
lastGuessPanel.Visible = true;
lastGuessLabel.Text = guess.ToString();
lowerLabel.Visible = guess < answer;
higherLabel.Visible = guess > answer;
yield return null;
}
mainMultiView.SetActiveView(resultView);
guessedNumberLabel.Text = answer.ToString();
attemptsLabel.Text = attempts.ToString();
}
We have to register enumerator with ContinuationManager
control and then make first call.
controlManager.Register(NumberGuess());
controlManager.Call();
It will update continuation data in ViewState
container
and return control immediately after first yield
return
statement. After that page will be rendered and submitted to the browser.
When next request is being processed continuation will restore itself form
ViewState
and all you need to do is make another call to
continue execution of the interrupted code. Since enumerator instance refers to
current page the user interface can be changed to display new input controls or
messages.
Points of Interest
Full description of C# 2.0 “unfolding” of iterator block into enumerator can
be found in “C# 2.0
Specification”. We can notice that the enumerator contains all the local
variables, pointer to class that declares that method (unless the method is
static), state and current returned value. The enumerator type is declared as
private nested class. The enumerator data can be imported and exported using
System.Reflection
classes. (see implementation of Continuation<T>
class)
ContinuationManager
server control should be located on
the control that declares iteration block. That helps to automatically find the
owner control of the restored enumerator.
Special attention is required for objects that implements IDisposable
interface. Usually a IDisposable
object isn’t marked as Serializable
therefore it is
impossible to import/export its state. However the objects can be used between
yield
statements.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.