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.

Leave a Reply