Yield, Web Continuations!






4.38/5 (9 votes)
Article shows how to emulate web continuation in .NET framework
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;
}
}
...
// Prints 1 1 2 3 5 8 13 21 34 55
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(); // generate random number
int answer = random.Next(1, 101);
int attempts = 1;
mainMultiView.SetActiveView(guessView);
lastGuessPanel.Visible = false;
yield return null; // store and return control
while(guess != answer)
{
++attempts;
lastGuessPanel.Visible = true;
lastGuessLabel.Text = guess.ToString();
lowerLabel.Visible = guess < answer;
higherLabel.Visible = guess > answer;
yield return null; // store and return control
}
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.