The static property Current on the HttpContext class can be useful whenever the flow of control leaves the code in your Page derived web form. Using this property you can reach out and magically grab the current Request, Response, Session, and Application objects (and more) for the request you are servicing. Take the following code as an example.
private void Page_Load(object sender, System.EventArgs e)
{
MyClass myClass = new MyClass();
myClass.DoFoo();
}
And - in some other assembly…
class MyClass
{
public void DoFoo()
{
HttpContext.Current.Response.Write("Doing Foo");
}
}
The ability to grab the context for the current request from any code inside the same application domain is powerful, but can also be misused. You can break the boundaries of your architectural layers using HttpContext.Current from a business object, and easily tie classes to ASP.NET that would otherwise work without change in Windows Forms or on a PDA with the Compact Framework.
The curious among us will wonder just how HttpContext.Current can find the context for the current request. In addition, does it always find the current request? For example, what is the behavior in the following code?
private void Page_Load(object sender, System.EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}
public void DoWork(object state)
{
HttpContext context = HttpContext.Current;
context.Response.Write("Do Work");
}
Answer: the above code generates a System.NullReferenceException because HttpContext.Current returns null. From a design point of view there are at least two problems in the above code, but let’s see how HttpContext.Current works before we discuss them.
A quick peek with a decompiler shows the implementation for the Current property looks something like the following.
public static HttpContext get_Current()
{
return (CallContext.GetData("HtCt") as HttpContext);
}
The CallContext provides a service extremely similar to thread local storage (except CallContext can perform some additional magic during a remoting call). Thread local storage is a concept where each logical thread in an application domain has a unique data slot to keep data specific to itself. Threads do not share the data, and one thread cannot modify the data local to a different thread. ASP.NET, after selecting a thread to execute an incoming request, stores a reference to the current request context in the thread’s local storage. Now, no matter where the thread goes while executing (a business object, a data access object), the context is nearby and easily retrieved.
Knowing the above we can state the following: if, while processing a request, execution moves to a different thread (via QueueUserWorkItem, or an asynchronous delegate, as two examples), HttpContext.Current will not know how to retrieve the current context, and will return null. You might think one way around the problem would be to pass a reference to the worker thread, like the following example.
private void Page_Load(object sender, System.EventArgs e)
{
WorkerClass2 worker = new WorkerClass2();
ThreadPool.QueueUserWorkItem(new WaitCallback(worker.DoWork), HttpContext.Current);
}
//…
class WorkerClass2
{
public void DoWork(object state)
{
HttpContext context = state as HttpContext;
Thread.Sleep(15000);
context.Response.Write("Request.Url = " + context.Request.Url);
}
}
However, in my environment the above code also throws an exception, albeit this time from the depths of mscoree. Both the above code and the first example with QueueUserWorkItem suffer from another flaw: both can outlive the lifetime of the page request, and also the valid lifetime of the HttpContext object assigned to that page request. While we can keep a reference to the HttpContext of any request to prevent the garbage collector from taking it, the ASP.NET runtime is certainly free to clear some of the resources as soon as the page request has finished processing. I don’t believe there is anyway to say exactly what will happen with the above code, on some machines in different circumstances the code may actually work, but the chance of failure certainly exists and the condition should be avoided.
There are ways to guarantee the page request does not finish until the worker thread completes its work, for example, the following code.
private void Page_Load(object sender, System.EventArgs e)
{
WorkerClass worker = new WorkerClass(_resetEvent);
ThreadPool.QueueUserWorkItem(new WaitCallback(worker.DoWork),
HttpContext.Current);
try
{
_resetEvent.WaitOne();
}
finally
{
_resetEvent.Close();
}
}
AutoResetEvent _resetEvent = new AutoResetEvent(false);
…
class WorkerClass
{
public WorkerClass(AutoResetEvent resetEvent)
{
_resetEvent = resetEvent;
}
public void DoWork(object state)
{
try
{
HttpContext context = state as HttpContext;
Thread.Sleep(500);
context.Response.Write("Do work");
}
finally
{
_resetEvent.Set();
}
}
AutoResetEvent _resetEvent = null;
}
The above code functions correctly and “Do work” appears in the browser. However, there are still some design points to ponder. First, the ASP.NET runtime processes multiple requests using a finite number of threads. We have just completed the same amount of work, but we’ve doubled the number of threads required, generated additional context switches, and now have a synchronization primitive to manage. If you can perform additional work in the original thread while waiting for the worker task to finish, then there might be a benefit. Generally, however, you should approach the idea of using additional threads in ASP.NET with a certain amount of reservation.
In this article we’ve gained some insight into how HttpContext.Current works, and seen some scenarios where we need to exercise caution. Don’t abuse the power HttpContext.Current gives you, and examine your design and architecture whenever the call appears inside your code.
Update: Fritz Onion has an excellent article on MSDN: Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code.
private void Page_Load(object sender, System.EventArgs e)
{
MyClass myClass = new MyClass();
myClass.DoFoo();
}
And - in some other assembly…
class MyClass
{
public void DoFoo()
{
HttpContext.Current.Response.Write("Doing Foo");
}
}
The ability to grab the context for the current request from any code inside the same application domain is powerful, but can also be misused. You can break the boundaries of your architectural layers using HttpContext.Current from a business object, and easily tie classes to ASP.NET that would otherwise work without change in Windows Forms or on a PDA with the Compact Framework.
The curious among us will wonder just how HttpContext.Current can find the context for the current request. In addition, does it always find the current request? For example, what is the behavior in the following code?
private void Page_Load(object sender, System.EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork));
}
public void DoWork(object state)
{
HttpContext context = HttpContext.Current;
context.Response.Write("Do Work");
}
Answer: the above code generates a System.NullReferenceException because HttpContext.Current returns null. From a design point of view there are at least two problems in the above code, but let’s see how HttpContext.Current works before we discuss them.
A quick peek with a decompiler shows the implementation for the Current property looks something like the following.
public static HttpContext get_Current()
{
return (CallContext.GetData("HtCt") as HttpContext);
}
The CallContext provides a service extremely similar to thread local storage (except CallContext can perform some additional magic during a remoting call). Thread local storage is a concept where each logical thread in an application domain has a unique data slot to keep data specific to itself. Threads do not share the data, and one thread cannot modify the data local to a different thread. ASP.NET, after selecting a thread to execute an incoming request, stores a reference to the current request context in the thread’s local storage. Now, no matter where the thread goes while executing (a business object, a data access object), the context is nearby and easily retrieved.
Knowing the above we can state the following: if, while processing a request, execution moves to a different thread (via QueueUserWorkItem, or an asynchronous delegate, as two examples), HttpContext.Current will not know how to retrieve the current context, and will return null. You might think one way around the problem would be to pass a reference to the worker thread, like the following example.
private void Page_Load(object sender, System.EventArgs e)
{
WorkerClass2 worker = new WorkerClass2();
ThreadPool.QueueUserWorkItem(new WaitCallback(worker.DoWork), HttpContext.Current);
}
//…
class WorkerClass2
{
public void DoWork(object state)
{
HttpContext context = state as HttpContext;
Thread.Sleep(15000);
context.Response.Write("Request.Url = " + context.Request.Url);
}
}
However, in my environment the above code also throws an exception, albeit this time from the depths of mscoree. Both the above code and the first example with QueueUserWorkItem suffer from another flaw: both can outlive the lifetime of the page request, and also the valid lifetime of the HttpContext object assigned to that page request. While we can keep a reference to the HttpContext of any request to prevent the garbage collector from taking it, the ASP.NET runtime is certainly free to clear some of the resources as soon as the page request has finished processing. I don’t believe there is anyway to say exactly what will happen with the above code, on some machines in different circumstances the code may actually work, but the chance of failure certainly exists and the condition should be avoided.
There are ways to guarantee the page request does not finish until the worker thread completes its work, for example, the following code.
private void Page_Load(object sender, System.EventArgs e)
{
WorkerClass worker = new WorkerClass(_resetEvent);
ThreadPool.QueueUserWorkItem(new WaitCallback(worker.DoWork),
HttpContext.Current);
try
{
_resetEvent.WaitOne();
}
finally
{
_resetEvent.Close();
}
}
AutoResetEvent _resetEvent = new AutoResetEvent(false);
…
class WorkerClass
{
public WorkerClass(AutoResetEvent resetEvent)
{
_resetEvent = resetEvent;
}
public void DoWork(object state)
{
try
{
HttpContext context = state as HttpContext;
Thread.Sleep(500);
context.Response.Write("Do work");
}
finally
{
_resetEvent.Set();
}
}
AutoResetEvent _resetEvent = null;
}
The above code functions correctly and “Do work” appears in the browser. However, there are still some design points to ponder. First, the ASP.NET runtime processes multiple requests using a finite number of threads. We have just completed the same amount of work, but we’ve doubled the number of threads required, generated additional context switches, and now have a synchronization primitive to manage. If you can perform additional work in the original thread while waiting for the worker task to finish, then there might be a benefit. Generally, however, you should approach the idea of using additional threads in ASP.NET with a certain amount of reservation.
In this article we’ve gained some insight into how HttpContext.Current works, and seen some scenarios where we need to exercise caution. Don’t abuse the power HttpContext.Current gives you, and examine your design and architecture whenever the call appears inside your code.
Update: Fritz Onion has an excellent article on MSDN: Use Threads and Build Asynchronous Handlers in Your Server-Side Web Code.
No comments:
Post a Comment