Using HostingEnvironment.QueueBackgroundWorkItem to run background tasks in ASP.NET

What is this new API?

.NET Framework 4.5.2 has released few new APIs for ASP.NET one of them is HostingEnvironment.QueueBackgroundWorkItem .Any one who has tried to run background tasks “reliably” in ASP.NET must have felt relieved with this.

So what’s the big deal with running background tasks in ASP.NET?

Well you can go through an excellent post from Phil Haack where he explains what problems can come while running the background tasks in ASP.NET and how to handle one of those scenarios using IRegisteredObject  interface.

Why do we need this?

Basically when you run a background task (using timers or TPL) in ASP.NET  and due to some reason app domain crashes (may be an IIS reset or Application pool recycle) then the whole process along with your background task will be aborted and any processing which you are doing will remain in an intermediate state.This is because IIS is not aware of your  task running in background.

Before framework 4.5.2 you had to use an implementation shown in above post to run the task reliably i.e. even if something happens, your task will have options of doing something about it.

In .net 4.5.2 QueueBackgroundWorkItem  makes this quite simple by providing a wrapper over IRegisteredObject  implementation.

How does it work?

Its simple. There are two versions of this API as shown below.

QueueBackgroundWorkItem(Action<CancellationToken>)

First version takes an Action delegate with cancellation token as parameter.This would be used in a scenario where you probably want start a Fire and Forget task.

QueueBackgroundWorkItem(Func<CancellationToken, Task>)

Second version takes Func delegate with a cancellation token as parameter and returns a Task.

Now any task that completes within 30 seconds is guaranteed to complete using this approach.Why ?…Because  before IIS shuts down  ASP.NET gives 30 second to all the background processes running and registered with it to complete.So even with this API you cannot be sure that your task will complete but you will have a mechanism to handle graceful exit .

The API takes CancellationToken  as parameter and this need to be monitored at all points in you code where you want to check whether or not process is being cancelled(due to some unexpected scenario) and do some cancellation processing or just let the process finish if you know it will complete within 30 seconds.

How to use it ?

Lets see a simple example of using this API demonstrating above concepts.

But first in case you don’t have .NET framework 4.5.2 you can download it from this location.Make sure you download “Microsoft .NET Framework 4.5.2 Developer Pack” as this will let you develop application targeting 4.5.2 framework using Visual Studio.

In this example there is one method which generates a random number with a delay of 3 seconds and overall 10 such numbers are generated.After the number are generated they are written to a local file.We will initiate this as a background task from a MVC action method using QueueBackgroundWorkItem  method.Below is the code.

public class Processor
    {
        const string filePath = @"<FilePath>";

        /// <summary>
        /// An operation which generates a random number in 3 seconds.
        /// </summary>
        /// <returns>Random number</returns>
        private int LongRunningOperation()
        {
            Thread.Sleep(3000);
            var rand = new Random();
            return rand.Next();
        }

        /// <summary>
        /// Method loops 10 times calling LongRunningOperation method to get a random number
        /// </summary>
        /// <param name="cancellationToken"></param>
        public void StartProcessing(CancellationToken cancellationToken=default(CancellationToken))
        {
            var result = new List<int>();
            try
            {
                for (int index = 1; index <= 10; index++)
                {

                    cancellationToken.ThrowIfCancellationRequested();
                    result.Add(LongRunningOperation());
                }
                WriteResultsToFile(result);
            }
            catch (Exception ex)
            {
                ProcessCancellation(result);
                File.AppendAllLines(filePath, new List<string>() { ex.GetType().ToString() + " : " + ex.Message });
            }
            
        }
        /// <summary>
        /// Writes result to a file
        /// </summary>
        /// <param name="numbers"></param>
        /// <param name="completed"></param>
         private void WriteResultsToFile(List<int> numbers,bool completed = true)
        {
            var content = new List<String>();
            string seed = string.Empty;
            
            content.Add("Results:" + numbers.Aggregate(seed, (s, n) => s + " " + n));
            if(!completed)
            {
                content.Add("Operation cancelled!!!");
            }
            File.Delete(filePath);
            File.AppendAllLines(filePath, content);
        }
        /// <summary>
        /// Method to hand the cancellation.
        /// </summary>
        /// <param name="numbers"></param>
        private void ProcessCancellation(List<int> numbers)
         {
             Thread.Sleep(10000);
             WriteResultsToFile(numbers, false);
         }
    }

Notice the loop in StartProcessing method.Here we monitor the cancellation token using cancellationToken.ThrowIfCancellationRequested  method.So in case a cancellation is requested this statement with throw an OperationCancelledException.

In our example whenever exception occurs we will call ProcessCancellation method.This method will basically insert a delay of 10 seconds (to demonstrate some kind of cancellation processing) and then write whatever numbers are calculated to result file along with exception message.

In case you don’t want to throw exception on cancellation you can use IsCancellationRequested   property as shown below.

if(cancellationToken.IsCancellationRequested )
{
// Process cancellation
}

Below is the code to start this as a background task .

 public class ProcessController : Controller
    {
        //
        // GET: /Primes/

        public ActionResult Index()
        {
            return View();
        }
        [HttpPost]
        public ActionResult Process()
        {
            
            HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => new Processor().StartProcessing(cancellationToken));
            
            return View("Index");
        }
    }

Recommended Books on Amazon:

Pro .NET 4 Parallel Programming in C# (Expert’s Voice in .NET)

.NET 4.5 Parallel Extensions Cookbook

Professional Parallel Programming with C#: Master Parallel Extensions with .NET 4

Demo

To demonstrate this lets run the application.In first run, we will let the process complete fully.

This is a simple ASP.NET MVC application with one button which will start the processing.

image

Below are the results.

image

Now to demonstrate the “crash” scenario I will start the process and then reset the IIS :-).

Well below are the results in case of crash scenario.

image

Few more thoughts….

Now in case we would have used TPL to start the process as shown below

Task.Factory.StartNew(() => new Processor().StartProcessing());

you would have still got an exception on “crash” viz. ThreadAbortException  but it would have exited immediately and would not have given 30 seconds to do final processing.

For example in above code one optimization which I can do is to see if loop index is greater than 6 i.e.  if six numbers are already generated before process crashed, I can let the process complete (instead of processing cancellation) as we are sure that next 4 numbers can be generated within 30 seconds along with file write operation.

Hope this post gives you a good idea about how this new API works.

Tagged on: , ,

3 thoughts on “Using HostingEnvironment.QueueBackgroundWorkItem to run background tasks in ASP.NET

  1. Joseph

    Good article.
    I do have a question. Instead of writing the results to the text file. I want to result to the client UI on the web page. I have looked around for this solution but I can’t find any. Any Ideas???

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.