Shared Variables in ASP.NET
By: Yorai Aminov
Abstract: Variable types are often misused in ASP.NET applications. This article discusses the various types of shared variables in Delphi ASP.NET applications
ASP.NET applications are multi-threaded, so it is usually a bad idea to use global variables in them. However, it is often necessary to use something similar – variables whose scope is not limited to a single function, method, or class. Delphi supports several types of “shared” variables that can be accessed globally, but these are often misused. Because of the difficulty involved in testing and debugging multi-threaded applications, such mistakes often go unnoticed, and only occur when the application is deployed on a busy web server.
The easiest way to understand the difference between the various variable types is to see them in action. The program has a single page, default.aspx, showing a single text box and a button. Pushing the button performs the following steps:
The third step is the crucial one, since it gives us enough time to run a second request while the first request is still being processed, and examine the effect of concurrent requests on the application. The complete source code for the page can be found at the end of this article.
The program contains several classes, descending from a common ancestor, each demonstrating a different type of variable. All classes descend from the Shared class, which defines a common interface:
type Shared = class abstract public class procedure SetValue(v: Integer); virtual; abstract; class function GetValue: Integer; virtual; abstract; end;
type
class
abstract
public
procedure
virtual
function
end
Global variables are allocated when the application starts, and persist for the duration of the program. A global variable has only a single instance, and all threads access the same variable. The SharedGlobalVar class uses a global variable to store the value used by the SetValue and GetValue methods.
.NET doesn’t support global functions. Because class methods and variables can be accessed without instantiating the class, they can be used to provide identical functionality. The SharedClassVar class uses a class variable to store the value used by the SetValue and GetValue methods.
Thread variables, declared using the threadvar keyword, are like global variables, but each thread gets a separate copy of the variable. The SharedThreadVar class uses a thread variable to store the value used by the SetValue and GetValue methods.
Thread static variables are class variables decorated with .NET’s [ThreadStatic] attribute. The attribute causes each thread to get a separate copy of the class variable. The SharedThreadStaticVar class uses such a variable to store the value used by the SetValue and GetValue methods.
Finally, the SharedRequestVar class uses ASP.NET’s HttpRequest object to store the value used by the SetValue and GetValue methods. An HttpRequest object is created for each ASP.NET request.
Instead of storing the value, the SharedRequestVar class uses a simple field, and creates an instance of itself using the Current method. The method checks the HttpRequest object for an existing instance, and only creates a new instance if a previous one cannot be found:
class function SharedRequest.Current: SharedRequest; begin if Assigned(HttpContext.Current.Items['SharedRequest']) then Result := HttpContext.Current.Items['SharedRequest'] as SharedRequest else begin Result := SharedRequest.Create; HttpContext.Current.Items['SharedRequest'] := Result; end; end; class function SharedRequest.GetValue: Integer; begin Result := Current.FValue; end; class procedure SharedRequest.SetValue(v: Integer); begin Current.FValue := v; end;
begin
if
'SharedRequest'
then
as
else
To test the program, we open two browser windows, and point them to our test page. Each browser window will post a different value. While a request is running in the first window, we’ll post another value from the second window, and wait for the results:
Hide image
If we enter “10” in the first window, click the “Run” button, then enter “20” in the second window and click its “Run” button (while the first request is still running), we get the following results:
Green labels represent expected values, while red labels represent bad results. Just by checking the colors, we can already see that both class variables and global variables can’t be used for concurrent requests. Both variable types exhibit the same behavior: clicking the “Run” button on the second window changed the values used by the first request, while it was running.
On the other hand, the thread-specific variables seem to have worked just fine. On both requests, these variables start with a value of 0, and end up with the correct value entered in the text box. If we execute another request in the first window, however, we discover a problem:
This time, the initial values of the thread-specific variables are no longer 0. This is because ASP.NET re-uses threads. In this case, the thread used to run the second request was used again to run the new request. Thread variables are not initialized when the thread resumes, which means the code cannot assume any initial value for these variables.
The “request” variable, however, stored in .NET’s HttpRequest object, always works. Because a new instance is created once and only once for each request, and because we used a field which is always initialized to 0, we can use the value as if it were global, while being certain no other requests – past, future, or concurrent – will interfere.
It seems obvious that global variables should be avoided in multi-threaded applications (unless, of course, they’re absolutely necessary and all access to them is fully synchronized), but developers often forget that class variables suffer from the same limitation. Thread variables, or class variables with the [ThreadStatic] attribute can be used in multi-threaded applications, but applications cannot assume these variables have been initialized.
ASP.NET’s current request (HttpRequest.Current) can always be assumed to be created when a request starts, and discarded when the request ends. This allows the creation of thread-safe, globally accessible, initialized variables.
While using the HttpRequest object is specific to ASP.NET, the methods outlined in this article should apply to any multi-threaded middle-tier technology, including web applications, data access layers, and business logic layers.
<%@ Page language="c#" Debug="true" Codebehind="Default.pas" AutoEventWireup="false" Inherits="Default.TDefault" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head runat="server"> <title>Shared variable types </title> </head> <body> <form runat="server"> <p>Enter a test value: <asp:TextBox id="ValueEdit" runat="server"></asp:TextBox> <asp:Button id="RunButton" runat="server" text="Run"></asp:Button></p> <table cellspacing="1" cellpadding="1"> <tr> <td><asp:Label id="ClassLabel" runat="server"></asp:Label></td> <td><asp:Label id="ThreadLabel" runat="server"></asp:Label></td> <td><asp:Label id="ThreadStaticLabel" runat="server"></asp:Label></td> <td><asp:Label id="RequestLabel" runat="server"></asp:Label></td> <td><asp:Label id="GlobalLabel" runat="server"></asp:Label></td> </tr> </table> </form> </body> </html>
unit Default; interface uses System.Collections, System.ComponentModel, System.Data, System.Drawing, System.Web, System.Web.SessionState, System.Web.UI, System.Web.UI.WebControls, System.Web.UI.HtmlControls, System.Web.Security, System.Web.UI.WebControls.WebParts, System.Configuration; type TDefault = class(System.Web.UI.Page) {$REGION 'Designer Managed Code'} strict private procedure InitializeComponent; procedure RunButton_Click(sender: System.Object; e: System.EventArgs); {$ENDREGION} strict private procedure Page_Load(sender: System.Object; e: System.EventArgs); strict protected ValueEdit: System.Web.UI.WebControls.TextBox; RunButton: System.Web.UI.WebControls.Button; ClassLabel: System.Web.UI.WebControls.Label; ThreadLabel: System.Web.UI.WebControls.Label; RequestLabel: System.Web.UI.WebControls.Label; GlobalLabel: System.Web.UI.WebControls.Label; ThreadStaticLabel: System.Web.UI.WebControls.Label; protected procedure OnInit(e: EventArgs); override; private function FormatResults(Title: string; StartValue, ChangedValue, FinalValue: Integer): string; public { Public Declarations } end; Shared = class abstract public class procedure SetValue(v: Integer); virtual; abstract; class function GetValue: Integer; virtual; abstract; end; SharedClassVar = class(Shared) private class var FValue: Integer; public class procedure SetValue(v: Integer); override; class function GetValue: Integer; override; end; SharedThreadVar = class(Shared) public class procedure SetValue(v: Integer); override; class function GetValue: Integer; override; end; SharedThreadStaticVar = class(Shared) private class var [ThreadStatic] FValue: Integer; public class procedure SetValue(v: Integer); override; class function GetValue: Integer; override; end; SharedRequest = class(Shared) private FValue: Integer; public class procedure SetValue(v: Integer); override; class function GetValue: Integer; override; class function Current: SharedRequest; end; SharedGlobalVar = class(Shared) public class procedure SetValue(v: Integer); override; class function GetValue: Integer; override; end; implementation uses System.Text, System.Threading; threadvar SharedValue: Integer; var GlobalValue: Integer = 0; {$REGION 'Designer Managed Code'} /// <summary> /// Required method for Designer support -- do not modify /// the contents of this method with the code editor. /// </summary> procedure TDefault.InitializeComponent; begin Include(Self.RunButton.Click, Self.RunButton_Click); Include(Self.Load, Self.Page_Load); end; {$ENDREGION} procedure TDefault.Page_Load(sender: System.Object; e: System.EventArgs); begin // TODO: Put user code to initialize the page here end; procedure TDefault.OnInit(e: EventArgs); begin // // Required for Designer support // InitializeComponent; inherited OnInit(e); end; procedure TDefault.RunButton_Click(sender: System.Object; e: System.EventArgs); var ClassStartValue: Integer; ClassChangedValue: Integer; ClassFinalValue: Integer; ThreadStartValue: Integer; ThreadChangedValue: Integer; ThreadFinalValue: Integer; ThreadStaticStartValue: Integer; ThreadStaticChangedValue: Integer; ThreadStaticFinalValue: Integer; RequestStartValue: Integer; RequestChangedValue: Integer; RequestFinalValue: Integer; GlobalStartValue: Integer; GlobalChangedValue: Integer; GlobalFinalValue: Integer; begin { Save initial values } ClassStartValue := SharedClassVar.GetValue; ThreadStartValue := SharedThreadVar.GetValue; ThreadStaticStartValue := SharedThreadStaticVar.GetValue; RequestStartValue := SharedRequest.GetValue; GlobalStartValue := SharedGlobalVar.GetValue; { Set statup values } SharedClassVar.SetValue(Convert.ToInt32(ValueEdit.Text)); SharedThreadVar.SetValue(Convert.ToInt32(ValueEdit.Text)); SharedThreadStaticVar.SetValue(Convert.ToInt32(ValueEdit.Text)); SharedRequest.SetValue(Convert.ToInt32(ValueEdit.Text)); SharedGlobalVar.SetValue(Convert.ToInt32(ValueEdit.Text)); { Save changed values } ClassChangedValue := SharedClassVar.GetValue; ThreadChangedValue := SharedThreadVar.GetValue; ThreadStaticChangedValue := SharedThreadStaticVar.GetValue; RequestChangedValue := SharedRequest.GetValue; GlobalChangedValue := SharedGlobalVar.GetValue; { Sleep for 10 seconds to allow user to run another request } Thread.Sleep(10000); { Get final values } ClassFinalValue := SharedClassVar.GetValue; ThreadFinalValue := SharedThreadVar.GetValue; ThreadStaticFinalValue := SharedThreadStaticVar.GetValue; RequestFinalValue := SharedRequest.GetValue; GlobalFinalValue := SharedGlobalVar.GetValue; { Display results } ClassLabel.Text := FormatResults( 'Class variable', ClassStartValue, ClassChangedValue, ClassFinalValue); ThreadLabel.Text := FormatResults( 'Thread variable', ThreadStartValue, ThreadChangedValue, ThreadFinalValue); ThreadStaticLabel.Text := FormatResults( 'Thread static variable', ThreadStaticStartValue, ThreadStaticChangedValue, ThreadStaticFinalValue); RequestLabel.Text := FormatResults( 'Request variable', RequestStartValue, RequestChangedValue, RequestFinalValue); GlobalLabel.Text := FormatResults( 'Global variable', GlobalStartValue, GlobalChangedValue, GlobalFinalValue); end; function TDefault.FormatResults(Title: string; StartValue, ChangedValue, FinalValue: Integer): string; var Builder: StringBuilder; begin Builder := StringBuilder.Create; Builder.Append('<p>'); Builder.Append(Title); Builder.Append('</p><ul>'); Builder.Append('<li style="color: '); if StartValue = 0 then Builder.Append('green') else Builder.Append('red'); Builder.Append('">Start: '); Builder.Append(StartValue); Builder.Append('</li>'); Builder.Append('<li>Changed: '); Builder.Append(ChangedValue); Builder.Append('</li>'); Builder.Append('<li style="color: '); if FinalValue = ChangedValue then Builder.Append('green') else Builder.Append('red'); Builder.Append('">Final: '); Builder.Append(FinalValue); Builder.Append('</li>'); Builder.Append('</ul>'); Result := Builder.ToString; end; { SharedClassVar } class function SharedClassVar.GetValue: Integer; begin Result := FValue; end; class procedure SharedClassVar.SetValue(v: Integer); begin FValue := v; end; { SharedThreadVar } class function SharedThreadVar.GetValue: Integer; begin Result := SharedValue; end; class procedure SharedThreadVar.SetValue(v: Integer); begin SharedValue := v; end; { SharedThreadStaticVar } class function SharedThreadStaticVar.GetValue: Integer; begin Result := FValue; end; class procedure SharedThreadStaticVar.SetValue(v: Integer); begin FValue := v; end; { SharedRequest } class function SharedRequest.Current: SharedRequest; begin if Assigned(HttpContext.Current.Items['SharedRequest']) then Result := HttpContext.Current.Items['SharedRequest'] as SharedRequest else begin Result := SharedRequest.Create; HttpContext.Current.Items['SharedRequest'] := Result; end; end; class function SharedRequest.GetValue: Integer; begin Result := Current.FValue; end; class procedure SharedRequest.SetValue(v: Integer); begin Current.FValue := v; end; { SharedGlobalVar } class function SharedGlobalVar.GetValue: Integer; begin Result := GlobalValue; end; class procedure SharedGlobalVar.SetValue(v: Integer); begin GlobalValue := v; end; end.
unit
Default
interface
uses
{$REGION 'Designer Managed Code'}
private
Object
{$ENDREGION}
protected
Label
override
string
{ Public Declarations }
var
implementation
threadvar
/// <summary>
/// Required method for Designer support -- do not modify
/// the contents of this method with the code editor.
/// </summary>
// TODO: Put user code to initialize the page here
//
// Required for Designer support
inherited
{ Save initial values }
{ Set statup values }
{ Save changed values }
{ Sleep for 10 seconds to allow user to run another request }
{ Get final values }
{ Display results }
'Class variable'
'Thread variable'
'Thread static variable'
'Request variable'
'Global variable'
'<p>'
'</p><ul>'
'<li style="color: '
'green'
'red'
'">Start: '
'</li>'
'<li>Changed: '
'">Final: '
'</ul>'
{ SharedClassVar }
{ SharedThreadVar }
{ SharedThreadStaticVar }
{ SharedRequest }
{ SharedGlobalVar }
Published on: 5/2/2008 4:18:46 AM
Server Response from: BDN10A
Borland® Copyright© 1994 - 2008 Borland Software Corporation. All rights reserved. Contact Us | Site Map | Legal Notices | Privacy Policy | Report Software Piracy