13

I have the following code in my codebehind (aspx.cs):

protected void button1_Click(object sender, EventArgs e)
{
    new Thread(delegate() {
        try
        {
            Thread.Sleep(30000); //do nothing for 30 seconds
        }
        catch (Exception ex)
        {
            //I AWLAYS get a ThreadAbortException here
            //in about 1 second WHY!?!??!
        }

    }).Start();
}

Does ASP.NET kill all child threads when the request ends?

PS. I predict a lot of "because it's not best practice" answers. I know about best practices of running things in a windows service etc. I'm just curious WHY EXACTLY the thread is being killed in this particular code, given there's no "Response.Redirect", no "Response.End", no pool recycle, no IIS-restart etc etc (typical reasons people are repeating the "background thread in ASP.NET is bad" mantra).

UPDATE: As it turns out it works fine in ASP.NET MVC!!! The thread is aborted in Web-Forms only!! Which is even more weird. Any ideas?

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
  • 5
    Do not start threads in response to a HTTP request – paulm May 05 '14 at 22:42
  • IIS reserves the right to kill any threads you produce. Solution: don't perform long running tasks in a web request. Use a technology specifically for that purpose - a Windows Service. – Simon Whitehead May 05 '14 at 22:42
  • 2
    I suppose because it's a background thread and you don't wait for it. So after asp.net finishes processing the request, it just kills it. – MikeSW May 05 '14 at 22:42
  • Threads should not be killed until AppDomain stays "alive" (no one touches web.config, no one recycles the pool etc) – Alex from Jitbit May 05 '14 at 22:44
  • 1
    @jitbit: who says threads should not be killed. The best practice has _always_ been to avoid long-running threads in response to ASP.NET requests. If nothing else, if your thread refers to the page, it's out of luck, as the page instance will be gone by then. – John Saunders May 05 '14 at 22:45
  • 1
    @jitbit I could not help chuckling a little. I have been reading a bit about this - the app pool will sort that out quick-quick. If you want, do a async callback instead and make that do the sleep. I expect you will have less hassles then. – Anthony Horne May 05 '14 at 22:45
  • ASP.NET requests are meant to be short lived. If anything runs for too long, a time-out will happen and all related threads get killed automatically. If you need long-running threads, consider utilizing a Windows service instead. – SF Lee May 05 '14 at 22:48
  • 1
    [This Article](http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/) gives a great explaination of this topic and why you should avoid it! – Icemanind May 05 '14 at 22:53

4 Answers4

19

Ok, so after 48 hours of experimenting here's what I found:

1. It's fine to run background threads in ASP.NET _MVC_

If you're on MVC - just start your threads as normal, they won't be aborted after the current request ends:

//MVC - works just fine
public ActionResult ThreadTest()
{
    new Thread(delegate() {
        try
        {
            System.Threading.Thread.Sleep(10000);
        }
        catch(Exception ex)
        {
            //no exception will be thrown
        }
    }).Start();
    return Content("ok");
}

2. For Web-Forms - use BackgroundWorker in combination with "Async" page attribute

I learned that with webforms you can't use:

  • new Thread().Start(); //not working

  • ThreadPool.QueueUserWorkItem //not working

  • Action.BeginInvoke //not working

  • A bunch of other things - all not working

Here's what you need to do:

1) Mark your page as "async" in .aspx (this will tell ASP.NET to inherit the page from IAsyncHandler)

<%@ Page language="c#" Async="true" %>

2) Use BackgroundWorker in .aspx.cs

protected void button1_Click(object sender, EventArgs e)
{
    var bg = new BackgroundWorker();
    bg.DoWork += delegate
    {
        try
        {
            Thread.Sleep(10000); //do nothing for 10 seconds
        }
        catch (Exception ex)
        {
            //no excpeiton is thrown
        }

    };
    bg.RunWorkerAsync();
}

3. If you're on .NET 4.5.2 or higher - simply use HostingEnvironment.QueueBackgroundWorkItem

"The HostingEnvironment.QueueBackgroundWorkItem method lets you schedule small background work items. ASP.NET tracks these items and prevents IIS from abruptly terminating the worker process until all background work items have completed. This method can't be called outside an ASP.NET managed app domain."

Kudos to @danny-tuppeny for this last one

Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
  • 1
    I didn't even know there was a .NET 4.5.2 yet. Cool new feature to know about, if need be. – cost May 08 '14 at 18:20
  • Running your code does not raise exception in my new empty project(webform) also look please [here](http://stackoverflow.com/questions/24361642/is-it-guaranteed-that-a-thread-will-complete-in-aspnet) - I thought that the end of the response should end the other threads - well I was wrong it doesnt end any thread. – Royi Namir Jun 23 '14 at 19:01
  • The name "background thread" here could be misleading. I would assume a background thread to be one created with `IsBackground` set to `true`. – Drew Noakes Jun 24 '16 at 08:38
5

The end of a response usually does kill a thread, yes. If you call Response.End you can even see that IIS throws a ThreadAbortedException to immediately terminate the thread. In general, you shouldn't be performing any long running tasks in IIS, especially not on a Task or Background Thread. For more information about why you should avoid this, as well as how you can force IIS to handle your long running operations (if you really must) check out these links:

Stack Overflow Question on long running jobs in IIS

The dangers of background tasks in asp.net

Community
  • 1
  • 1
cost
  • 4,420
  • 8
  • 48
  • 80
2

ASP.NET WebForms

Providing another solution: you don't need to set your page with the async property. The following code will work fine:

new Thread
(
    delegate()
    {
        try
        {
            MyMethod(myVar);
        }
        catch (Exception ex)
        {
            // handle
        }
    }
)
{
    IsBackground = true
}.Start();

In this code, IsBackground = true is what prevents the thread to be aborted when the request finishes.

Zanon
  • 29,231
  • 20
  • 113
  • 126
  • Thank you. With this approach, I find I can run an activity that takes a minute or two in the background thread, after spawning it from the aspx.cs code. I'm using this to run small tasks from time to time, on a hosting service. – David Pierson Oct 30 '21 at 01:14
1

ASP.NET worker process unloads virtual applications if there are no requests hitting aspx/asmx pages to save memory on IIS.

IIS is kicking out your thread because it is not a background thread.

On IIS there is this option:

<processModel
enable="true"
timeout="Infinite"
idleTimeout="Infinite"
shutdownTimeout="0:00:05"
requestLimit="Infinite"
requestQueueLimit="5000"
restartQueueLimit="10"
memoryLimit="60"
webGarden="false"
cpuMask="0xffffffff"
userName="machine"
password="AutoGenerate"
logLevel="Errors"
clientConnectedCheck="0:00:05"
comAuthenticationLevel="Connect"
comImpersonationLevel="Impersonate"
responseRestartDeadlockInterval="00:09:00"
responseDeadlockInterval="00:03:00"
maxWorkerThreads="20"
maxIoThreads="20"
/>
Bura Chuhadar
  • 3,653
  • 1
  • 14
  • 17