Friday, March 27, 2009

Shared Variables in ASP.NET

Source : http://dn.embarcadero.com/article/38007

 

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

    Introduction

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 Test Program

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:

  1. Retrieve the initial values of each of the variable types.
  2. Set all variables to the value typed in the text box.
  3. Wait for 10 seconds.
  4. Display the initial, entered, and final values of the variables.

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.

    Variable Types

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; 

    Global Variables

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.

    Class Variables

.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

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

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.

    Request Variables

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; 

    Running the Program

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
Click to see full-sized 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:

Hide image
Click to see full-sized image

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:

Hide image
Click to see full-sized image

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.

    Conclusions

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.

    Source Code

    Default.aspx

<%@ 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>

    Default.pas

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. 


Dapatkan nama yang Anda sukai!
Sekarang Anda dapat memiliki email di @ymail.com dan @rocketmail.com.

0 Comments: