Exploring QueueBackgroundWorkItem in ASP.NET and Framework 4.5.2

Sometimes it’s very useful for long running tasks that don’t need to complete before returning a response to the user in ASP.NET application.

But before  .NET 4.5.2 release , we were not sure that these tasks have been executed safely.

In the release notes, QueueBackgroundWorkItem is summarized as follows:

HostingEnvironment.QueueBackgroundWorkItem 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.

The benefit from this is reliably. If you use HostingEnvironment queue in an ASP.NET application, any background tasks are guaranteed to execute safely.

How does QueueBackgroundWorkItem works ?, as follow :

using System.Web.Mvc;
using System.Web.Hosting;

namespace MyApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            HostingEnvironment.QueueBackgroundWorkItem(clt =>
            {
                //Background task that needs to be performed safely
            });
            return View();
        }

    }
}

Note that HostingEnvironment.QueueBackgroundWorkItem belongs to System.Web.Hosting NameSpace.

This method defines two overloads:

  • Action
  • Func

Firstly, here are samples with Action overload :

We will define long running actions : a classical Task and an async Task:

using System.Web.Mvc;
using System.Web.Hosting;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace MyApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            HostingEnvironment.QueueBackgroundWorkItem(clt=>
            {
                //Background task that needs to be performed safely
            });
            return View();
        }

        //Action overload's target
        private void LongRunningAction(CancellationToken clt)
        {
            Task.Run(() => { Thread.Sleep(5000);
                             Debug.WriteLine("Action executed"); 
                           });
        }

        //Action overload's target
        private async void LongRunningActionAsync(CancellationToken clt)
        {
            await Task.Run(() => { Thread.Sleep(5000); 
                                   Debug.WriteLine("Action async executed"); 
                                 });
        }
    }
}

Now let’s see ways to use it:

using System.Web.Mvc;
using System.Web.Hosting;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System;

namespace MyApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            //Sample 1
            //Action overload
            //with lambda expression
            HostingEnvironment.QueueBackgroundWorkItem(
                clt => LongRunningAction(clt)
            );

            //Sample 2
            //Action overload
            //without lambda expression
            HostingEnvironment.QueueBackgroundWorkItem(
                (Action)LongRunningAction
            );

            //Sample 3
            //Action overload
            //with lambda expression
            HostingEnvironment.QueueBackgroundWorkItem(
                clt => LongRunningActionAsync(clt)
            );

            //Sample 4
            //Action overload
            //without lambda expression
            HostingEnvironment.QueueBackgroundWorkItem(
                await (Action)LongRunningAction
            );

            return View();
        }

        //Action overload's target
        private void LongRunningAction(CancellationToken clt)
        {
            Task.Run(() => { Thread.Sleep(5000); 
                             Debug.WriteLine("Action executed"); 
                           });
        }

        //Action overload's target
        private async void LongRunningActionAsync(CancellationToken clt)
        {
            await Task.Run(() => { Thread.Sleep(5000); 
                                   Debug.WriteLine("Action async executed"); 
                                 });
        }
    }
}

As you can see, you can execute Actions, with Lambda expression syntax (sample 1) or not (sample 2)

You can also execute async Actions, with Lambda expression syntax (sample 3) or not (sample 4)

Secondly, here are samples with Func overload :

We will define a function that return a long running Task :

using System.Web.Mvc;
using System.Web.Hosting;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace MyApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            HostingEnvironment.QueueBackgroundWorkItem(
                clt => { }
            );

            return View();
        }

        //Func overload's target
        private Task LongRunningFunc(CancellationToken clt)
        {
            return Task.Run(() => { Thread.Sleep(5000); 
                                    Debug.WriteLine("Func executed"); 
                                  });
        }
   
    }
}

Let’s see ways to use it :

using System.Web.Mvc;
using System.Web.Hosting;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;
using System;

namespace MyApp.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            //Sample 5
            //Func overload
            //With lambda expression
            HostingEnvironment.QueueBackgroundWorkItem(
                clt => LongRunningFunc(clt)
            );

            //Sample 6
            //Func overload
            //Without lambda expression
            HostingEnvironment.QueueBackgroundWorkItem(
                (Func)LongRunningFunc
            );

            //Sample 7
            //Func overload
            //With lambda expression
            //Accept async / await
            HostingEnvironment.QueueBackgroundWorkItem(
                async clt => await LongRunningFunc(clt)
            );

            return View();
        }

        //Func overload's target
        private Task LongRunningFunc(CancellationToken clt)
        {
            return Task.Run(() => { Thread.Sleep(5000); 
                                     Debug.WriteLine("Func executed"); 
                                  });
        }
   
    }
}

As you can see, you can execute functions, with Lambda expression syntax (sample 5) or not (sample 6)

You can also use async / await keywords to execute the function (sample 7)

Summary

As you’ve seen, the new QueueBackgroundWorkItem method is very easy to use with different delegate parameters. ASP.NET does the heavy lifting for us by preventing IIS from terminating worker processes when there are any pending background work items. Consequently, HostingEnvironment.QueueBackgroundWorkItem is an ideal candidate for scheduling small background jobs in .NET 4.5.2.

I have chosen an ASP.NET MVC for this article, of course, you can call the QueueBackgroundWorkItem method from another web application type (such as WebForm, and also WCF!), this is not exclusive to MVC.

Microsoft .NET Framework 4.5.2 Released

ASP.NET improvements

  • New HostingEnvironment.QueueBackgroundWorkItem method that 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. These will enable ASP.NET applications to reliably schedule Async work items.
  • New HttpResponse.AddOnSendingHeaders and HttpResponseBase.AddOnSendingHeaders methods are more reliable and efficient than HttpApplication.PreSendRequestContent and HttpApplication.PreSendRequestHeaders. These APIs let you inspect and modify response headers and status codes as the HTTP response is being flushed to the client application. These reliability improvements minimize deadlocks and crashes between IIS and ASP.NET.
  • New HttpResponse.HeadersWritten and HttpResponseBase.HeadersWritten properties that return Boolean values to indicate whether the response headers have been written. You can use these properties to make sure that calls to APIs such as HttpResponse.StatusCode succeeds. This enables shared hosting scenarios for ASP.NET applications.

You can download the release now : .NET 4.5.2 Framework

Source from .NET Framework blog