Why is threading so much work in a Web app?

Doing stuff in seperate threads in a Web app is a pain when you need a reference to general application state. Why does it have to be so hard?

The first time I did something in this realm, I was trying to make an asynchronous mailer to notify people in a forum thread that there was a reply to the thread. With some help from someone in the ASP.NET forums, I eventually arrived at something like this in the class (the GCHandle class is in System.Runtime.InteropServices, if you're curious):

public void Send()
{
 appHandle = GCHandle.Alloc(HttpContext.Current, GCHandleType.Normal);
 // notify by email those who are hooked up
 ThreadStart workerstart = new ThreadStart(Mailer);
 Thread myThread = new Thread(workerstart);
 myThread.Start();
}

protected GCHandle appHandle;

private void Mailer()
{
 HttpContext context = (HttpContext)appHandle.Target;
 // mail work here
}

This works perfectly, though I'm still not clever enough to understand exactly why. Adapting this same logic to a static Timer object in an HttpModule wasn't much harder, provided I kept passing along the context through whatever objects and code I wanted to run:

public void Init(HttpApplication application)
{
 appHandle = GCHandle.Alloc(application.Context, GCHandleType.Normal);
 myTimer = new Timer(new TimerCallback(this.TimerMethod), application.Context, 60000, 60000);
}

static Timer myTimer;
protected GCHandle appHandle;

private void TimerMethod(object sender)
{
 HttpContext context = (HttpContext)appHandle.Target;
 // do whatever
}

So now that I'm clever enough to follow this pattern, can anyone explain why exactly it works? I'm not satisfied without knowing the "why" along with the "how."

10 Comments

  • I'm not sure, but I can see a few problems with this:



    1. It doesn't scale as the threads are all from the same thead pool (ie. it takes thread from the same pool that ASP.NET uses, I think). And you don't own the pool, so you cannot explicitly say what's happening.



    2. It prevents Garbage Collection, in your case the whole object is hold on memory until you explicitly free appHandles.



    3. I'm not sure where your mailer needs HttpContext. Could you just instatiate new object with the parameters you need and execute it as a thread (but take the thread from somewhere else or you are not gaining anything in scalability, only in response times)? I think that this is a good place for Message Queue, eg. MSMQ.



    You should also know that one single request can be handled by multiple threads in ASP.NET. (That's why you cannot use [ThreadStatic] attributed data types in ASP.NET.)

  • The mailer needs the context to get settings and cached items.

  • > The mailer needs the context to get settings and cached items.



    So, can you just retrieve those settings? Can't you get the settings by using ConfigurationSettings.AppSettings for example? Or what do you mean about settings? What about cloning those objects that you need in a child thread?



    Keep in mind that your child thread does not own HttpContext, so it should not try to modify it. And the HttpContext is meaningless after main thread finishes the work. HttpContext is only maintained by ASP.NET threads (so that the threads (the ASP.NET threads) that process the request (one single request) can share data throught HttpContext. I don't know how it's implemented and even if I knew i would not depend on it.



    Executing your own threads in Web environment (ASP.NET, Java Servlet, etc.) is hardly ever a good idea. I think that you should redesign the code so that you don't have to do that.



    There are many choices:



    - Message Queues

    - Separate Service that polls eg. file system or data base changes on timely fashion.

    - Database might provide some tools

  • It frequently needs to get data from Application and Cache.



    You say that it's "hardly ever a good idea," and not to use HttpContext, but you don't give any reasons why. I'll take your advice if you give me a reason, but "I say so" isn't really a reason.



    In fact, this technique is used quite a bit in the ASP.NET forums, only without the handle. I'm not sure why it works for that app without it but not mine.

  • > You say that it's "hardly ever a good idea," and not to use

    > HttpContext



    I say, it's hardly ever a good idea to execute your own threads in a web environment. And I say that you should not use objects that are not managed by your own thread or thread pool. I mean it as a general principle. It's not written on a stone and you can do whatever you want, but these are dificult things to handle properly as you cannot know what happens behind the scenes (and the behavior can change - eg. configuration, multiple processors, asp.net version). And HttpContext is particularly bad example. HttpContext is object for a request.



    Example:



    (Note: HttpContext Encapsulates all HTTP-specific information about an individual HTTP request.)



    1. Request comes In (RequestContext is Created and it has a meaning)

    2. You start your own thread and pass a HttpContext to it.

    3. Main thread continues other stuff / Child thread does its stuff

    4. Main thread ends. REQUEST ENDS!

    5. Child thread continues its stuff

    6. Child thread uses HttpContext



    WHAT CONTEXT? There isn't any request anymore, it was served and gone and the child thread missed the train.



    This was my point.



    > In fact, this technique is used quite a bit in the ASP.NET

    > forums, only without the handle.



    Umm... Can you so my some code?

  • There's nothing wrong with doing multi-threading in a web application. The availability of the HttpApplication, the Cache and the curent HttpContext is something to be concerned about.



    With GCHandle.Alloc you're effectively prohibiting the GC from collecting the HttpContext (but this technique is normally used from unmanged code). Why aren't you passing a reference of HttpContext.Current to your thread (via a worker object)? Here's a little console app that demos the technique (not using HttpContext ofcourse). Even though my main thread dies, the Context object is not collected because another thread is holding onto it.



    using System;

    using System.Threading;



    namespace ThreadTester {

    class Default {

    [STAThread]

    static void Main(string[] args) {

    new MyWorker(new Context());

    Console.ReadLine();

    }

    }



    public class Context{

    public DateTime Then{

    get { return _then; }

    }DateTime _then = DateTime.Now;

    }



    public class MyWorker{

    Context _context;

    public MyWorker(Context context){

    _context = context;

    Thread thread = new Thread(new ThreadStart(DoWork));

    //thread.IsBackground = true;

    thread.Start();

    }

    private void DoWork(){

    while(true){

    Thread.Sleep(TimeSpan.FromSeconds(5));

    Console.WriteLine("Now:{0} Then:{1}", DateTime.Now, _context.Then);

    }

    }

    }

    }



    And here's the "same" code in a web app:

    using System;

    using System.Collections;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Web;

    using System.Web.SessionState;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using System.Web.UI.HtmlControls;

    using System.Threading;



    namespace ThreadTester {

    public class MyWorker{

    HttpContext _context = null;



    public MyWorker(HttpContext context){

    _context = context;

    Thread thread = new Thread(new ThreadStart(Run));

    thread.Start();

    }



    public void Run(){

    while(true){

    System.Diagnostics.Debug.WriteLine(String.Format("Now:{0} ApplicationCount:{1}", DateTime.Now, _context.Application.Count));

    Thread.Sleep(TimeSpan.FromSeconds(2));

    }

    }

    }



    public class WebForm1 : System.Web.UI.Page {

    private void Page_Load(object sender, System.EventArgs e) {

    new MyWorker(HttpContext.Current);

    }



    #region Web Form Designer generated code

    override protected void OnInit(EventArgs e) {

    InitializeComponent();

    base.OnInit(e);

    }



    private void InitializeComponent() {

    this.Load += new System.EventHandler(this.Page_Load);

    }

    #endregion

    }

    }

  • So everyone has code to prevent the Content from being collected, but what happens to resources the Context holds references to that are Disposed by the ASP.NET runtime because the request is finished? I think it's a tricky area to work in.

  • See my last comment here...

    http://weblogs.asp.net/jeff/archive/2004/08/20/217866.aspx

  • Creating you own threads from a Web app/ASP.NET is a generally not a good idea (but possible) because of the non-deterministic nature of the number of threads that will be running. ASP.NET maintians a thread pool with a specified maximum for a reason, allowing an infinite number of threads to be running is a deterimental to performance and scalability. For example, an ASP.NET with a single page with 25 worker threads servicing N number of clients/requests will never have more than 25 threads running. If that page creates a single thread during its processing, than you will have N*2 (potentially) active threads. It is my understanding that when you creat a thread manually it does not come from the ASP.NET worker process thread pool.



    This may be ok for your application, say 1000 users in geographically diverse locations - not all 1000 at a time. May even be ok if they all access the page simulataneously with sufficient hardware, but I don't consider having 2000 threads running on my server a good idea (in general). Take a look at the CPU performance counters to test the effects.

  • While I get what you're saying, generally speaking I'm talking about creating one thread via a Timer for the entire application, not individual new threads for each user.

Comments have been disabled for this content.