Notice: This website is an unofficial Microsoft Knowledge Base (hereinafter KB) archive and is intended to provide a reliable access to deleted content from Microsoft KB. All KB articles are owned by Microsoft Corporation. Read full disclaimer for more details.

Troubleshooting ASP.NET applications with the use of static keywords


View products that this article applies to.

Welcome to the Microsoft ASP.NET Support Voice column! My name is Jerry Orman. I have been with Microsoft over 5 years, and have spent most of my time focused on Web-related technologies such as Microsoft FrontPage and the new Microsoft SharePoint technologies. I have spent the last year working with Microsoft ASP.NET as a support engineer. This month in the Support Voice column, I am going to describe troubleshooting ASP.NET applications with the use of static keywords.

↑ Back to the top


Statics

This article discusses two different behaviors that are caused by static keywords and that can be difficult to troubleshoot. Hopefully after you read this article, you can avoid these behaviors in your applications and be able to better diagnose them if they occur.

Users see incorrect data

One symptom that is very difficult to troubleshoot occurs when a user submits data, but another user's information is displayed or is entered into a database. This behavior typically occurs because of incorrect use of static variables in classes. A static object is instantiated one time for each application domain, and all processing in the application shares the same object. For example, let's say you set up a static property in a class by using the following code.
using System;

namespace statics
{
	public class BadClass
	{
		private static string _MyData;

		public static string MyData
		{
			get{return _MyData;}
			set{_MyData = value;}
		}
	}
}
The MyData property is static and public. You can access it anywhere in your application by using the following syntax:

BadClass.MyData

So now imagine that you have a page that sets the value of MyData, and then does some work with it. If multiple users hit the page at the same time, you now have a race condition where the last user who updated MyData wins. The following sample shows this behavior by setting the static object during a button_click event. This sample also makes the thread sleep for 15 seconds. This allows both requests to run at the same time. To do this, follow these steps:
  1. Create an .aspx Web form.
  2. Add a label, a button, and a text box. The HTML will look similar to the following.
    <body>
    	<form id="Form1" method="post" runat="server">
    		<asp:Label id="Label1" runat="server"></asp:Label>
    		<br>
    		<asp:Button id="Button1" runat="server" Text="Button"></asp:Button>
    		</br>
    		<asp:TextBox id="TextBox1" runat="server"></asp:TextBox>
    	</form>
    </body>
    
    
  3. Double-click the button to create a button_click event.
    private void Button1_Click(object sender, System.EventArgs e)
    {
    	//Set the static property to the value of the text box.
    	BadClass.MyData = TextBox1.Text;
    
    	//Make the thread wait 30 seconds
    	System.Threading.Thread.Sleep(30000);
    
    	//Set the label to the value of the property.
    	Label1.Text = BadClass.MyData;
    }
  4. Open two separate Microsoft Internet Explorer processes:
    1. Click Start, point to All Programs, and then click Internet Explorer.
    2. Repeat step a.
    This will ensure that you have two separate Iexplore.exe processes running at the same time. You can check Task Manager to make sure. When you have two Iexplore.exe processes running at the same time, you have unique sessions for each page request. Requests that have the same session ID are processed in the order that they come in and do not run at the same time.
  5. Browse your .aspx page in both browser windows.
  6. Submit User1 in the text box of one browser window, and then submit User2 in the text box in the other browser window.

    After the pages complete, you will see User2 in both labels.
As mentioned previously, both pages are running at the same time. The first request sets the static property to User1, and then goes to sleep. While the thread is paused, another request comes in and sets the same object to User2. The thread that is processing the first request completes, and then displays the value. The value is set to User2.

Avoid this behavior

The best way to avoid this behavior is to not make the object static and to use an instance of the class to set and to retrieve the values. The property is no longer shared because a copy of the object is created for each request, and you will not see this behavior. To avoid this behavior, follow these steps:
  1. Remove the static modifier from the BadClass.cs file.
    using System;
    
    namespace statics
    {
    	public class BadClass
    	{
    		private string _MyData;  	//Removed static
    	
    		public string MyData		//Removed static
    		{
    			get{return _MyData;}
    			set{_MyData = value;}
    		}
    	}
    }
    
  2. Change the button_click event.
    private void Button1_Click(object sender, System.EventArgs e)
    {
    	//Create an instance of BadClass
            BadClass _bad = new BadClass();	
    	
    	//Set the MyData property on this instance
            _bad.MyData = TextBox1.Text;
    
            //Sleep for 30 seconds
    	System.Threading.Thread.Sleep(30000);
    
    	//Set the label to the value of the property
    	Label1.Text = _bad.MyData;
    }
    
If you have to use the static object, you have to implement locking when you are using the static object. This prevents both threads from accessing the object at the same time. To do this, follow these steps:
  1. Add a static object to your code behind the page. This object will be a shared resource that the pages can obtain a lock on.
    static object StaticLocker = new object();
  2. Change the button_click event to the following.
    private void Button1_Click(object sender, System.EventArgs e)
    {
    	//Lock the object to prevent multiple threads from writing to the static property
    	lock(StaticLocker)
    	{
    		//Set the static property
    		BadClass.MyData = TextBox1.Text;
    
    		//Put the thread to sleep for 30 seconds
    		System.Threading.Thread.Sleep(30000);
    
    		//Set the label to the value of the static property
    		Label1.Text = BadClass.MyData;
    	}
    }
    

NullReferenceException caused by statics

Another behavior that occurs when you use statics is a System.NullReferenceException exception. For example, you change your button_click event code to the following.
private void Button1_Click(object sender, System.EventArgs e)
{
	BadClass.MyData = TextBox1.Text;
	System.Threading.Thread.Sleep(10000);
	Label1.Text = BadClass.MyData.ToString();
	BadClass.MyData = null;
}
If you perform the same test as before, when BadClass.MyData.ToString() is called for the second request, it will throw a System.NullReferenceException exception. The System.NullReferenceException exception occurs because the first request set the value to null while the second request was waiting. The resolution for this behavior is the same as for a static property.
Memory leak caused by static events
If you set up a static event and then subscribe to that event from an .aspx page, the process will eventually run out of memory. Let's consider you add the following to the BadClass.cs file.
public delegate void MyEvent();
public static event MyEvent BadEvent;
You then wire up the event delegate in the InitializeComponent method of the .aspx page to a method in the page.
private void InitializeComponent()
{    
	//Add this with the other items (for example, this.Load).
	BadClass.BadEvent += new BadClass.MyEvent(this.TestEvent);
}
You also add the following method to your page. This is the method that you specified when hooking up the event in the InitializeComponent method:
private void TestEvent()
{
	//Do some work here.
}
Why this causes a memory leak
Because the event is static and never goes out of scope, you are adding the method to the list of events that are fired when the event occurs each time the page is run. The end result of this is that whatever object you wire to the static event is rooted in memory, and it will never be collected. In this case, that object is the actual .aspx page class.

This behavior occurs in the Windbg.exe debugger or the Cdb.exe debugger by running the !gcroot command on the object that you are adding the event from. You will see output that is similar to the following.
HANDLE(Strong):c3d11d8:Root:0x90b8838(System.Object[])->0x5346f68(statics.BadClass/MyEvent)->
0x10ff928(statics.BadClass/MyEvent)->
0x10faa24(statics.BadClass/MyEvent)->
Many object requests have been made to the following events.
0x10e6fe0(statics.BadClass/MyEvent)->
0x533baf0(ASP.StaticsTest_aspx)
You can see that the bottom item is the .aspx page class, and it is rooted to multiple MyEvent delegates. Because the .aspx page is rooted, it cannot be cleaned up. The other side effect is that whatever the .aspx page is using also cannot be cleaned up because they are rooted in the page object.

For more information about the Microsoft .NET garbage collector, visit the following MSDN Web sites:For more information about the !gcroot command and obtaining this output, see the ".NET CLR Memory Counters" section of the following MSDN Web site:

Avoid this behavior

To avoid this behavior, you can either not use the static keyword on the event or remove the event handler from the page when you are done using it. In an ASP.NET example, you wire the event when Page_Init is called. You must remove it when the page unloads by adding an event handler for the Page_Unload event.

Add the following to the InitializeComponents method.
this.Unload += new System.EventHandler(this.Page_Unload);

Add the following method to the page to remove the event:
private void Page_Unload(object sender, System.EventArgs e)
{
	BadClass.test -= new BadClass.MyEvent(this.TestEvent);
}
For more information about events, visit the following MSDN Web site:

↑ Back to the top


As always, feel free to submit ideas on topics you want addressed in future columns or in the Knowledge Base using the Ask For It form.

↑ Back to the top


Keywords: KB893666, kbasp, kbhowto

↑ Back to the top

Article Info
Article ID : 893666
Revision : 5
Created on : 3/27/2007
Published on : 3/27/2007
Exists online : False
Views : 401