Marius Schulz

Using QueueBackgroundWorkItem to Schedule Background Jobs from an ASP.NET Application in .NET 4.5.2

Starting with the recently released version 4.5.2 of the .NET Framework, ASP.NET now supports the HostingEnvironment.QueueBackgroundWorkItem method found in the System.Web.Hosting namespace. I want to quickly show you how you can use it to schedule background work items in an ASP.NET MVC application.

The HostingEnvironment.QueueBackgroundWorkItem Method in Action

What Does QueueBackgroundWorkItem Do?

In the release notes, the QueueBackgroundWorkItem method is summarized as follows:

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.

The summary (emphasis mine) highlights the reason for using QueueBackgroundWorkItem: You won't have to worry about processes being shut down prematurely by IIS.

Note that QueueBackgroundWorkItem can only be called inside an ASP.NET managed app domain. It won't work if the runtime host is either Internet Explorer or some Windows shell. For more information on app domains, please refer to Using Application Domains.

Basic Usage of the QueueBackgroundWorkItem Method

The QueueBackgroundWorkItem method defines two overloads, each of which accepts a single parameter. You can pass either of the following delegate types:

  • Action<CancellationToken>
  • Func<CancellationToken, Task>

Here's how you could pass a lambda expression to the first overload:

HostingEnvironment.QueueBackgroundWorkItem(cancellationToken =>
{
    // Some long-running job
});

The lambda can even be async so that you can use all the goodness await has to offer:

HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
    var result = await LongRunningMethodAsync();

    // Do something with result
    // ...
});

Passing a method group to the overload accepting a Func is possible as well:

private void QueueWorkItem()
{
    Func<CancellationToken, Task> workItem = LongRunningMethodAsync;
    HostingEnvironment.QueueBackgroundWorkItem(workItem);
}

private async Task LongRunningMethodAsync(CancellationToken cancellationToken)
{
    // Some long-running job
}

Due to the way that the C# compiler does method group conversion, it's not possible to pass LongRunningMethodAsync directly to QueueBackgroundWorkItem. The issue is that the compiler utilizes overload resolution when converting the method group, and overload resolution doesn't take into account return types.

Since both Action and Func overloads accept a single parameter of type CancellationToken, there's no way to distinguish the two method calls just by looking at their parameter types. The assignment to the workItem variable provides the compiler with that missing type information. For more detail, make sure to read this great StackOverflow answer from Eric Lippert Himself™.

Posting to a Remote API from an ASP.NET MVC Controller

Here's a more complete example of how QueueBackgroundWorkItem can be used in an ASP.NET MVC controller. After creating some Foo model, the controller registers a background work item which makes a (potentially) long-running call to a remote API:

public class FooController : Controller
{
    [HttpPost]
    public ActionResult Create(FooInputModel input)
    {
        // Process the input somehow
        // ...

        Action<CancellationToken> workItem = PostToRemoteService;
        HostingEnvironment.QueueBackgroundWorkItem(workItem);

        return View();
    }

    private async void PostToRemoteService(CancellationToken cancellationToken)
    {
        using (var client = new HttpClient())
        {
            var response = await client.PostAsync("http://example.com/endpoint",
                new StringContent("..."), cancellationToken);

            // Do something with response
            // ...
        }
   }

    // More action methods
    // ...
}

That way, the controller can return an ActionResult (in this case, a view) after the input has been processed. It doesn't have to wait until the HTTP request to the remote API has been made and a response is returned.

Of course, you can call the QueueBackgroundWorkItem method from other ASP.NET application types as well, it is in no way specific to MVC.

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.

For an overview of all the other new features that shipped with .NET 4.5.2, read the blog post from the .NET engineering team or the summary in the MSDN library:

Marius Schulz

Computer science student, developer, and blogger. I love C# and JavaScript, enjoy regular expressions, and probably drink too much coffee.

Next post: Implementing an Exception Helper Class for Parameter Null Checking
Previous post:

4 Comments

Kristian Hellang

Isn't the

Func<CancellationToken, Task> workItem = LongRunningMethodAsync;

line redundant? You could just do

HostingEnvironment.QueueBackgroundWorkItem(LongRunningMethodAsync);

?

Kristian Hellang

Great post, BTW :D

odinserj

Great post, thanks.

Unfortunately, the QueueBackgroundWorkItem method just wraps ThreadPool.QueueUserWorkItem call with HostingEnvironment.RegisterObject and HostingEnvironment.UnregisterObject and sends exception details to EventLog.

ASP.NET still use shutdown timeout, that cause your background jobs to be aborted after 30 sec by default. Yes, there is CancellationToken, and I can cancel my background job during shutdown event, and it will not be aborted. But I should be able to start this job again after application restart – and there is no such feature.

So, this solution satisfies only these cases, when background jobs are:

  • small enough to complete at shutdown for N seconds.
  • large, and you are going to implement retry logic (after restarts and after exceptions), but this is hard.

If you want to use background jobs in your application and want to stop thinking about reliability issues, try to use HangFire – it is designed to run background jobs inside ASP.NET applications with ease.

Marius Schulz

@Kristian: Without assigning the method group to the explicitly typed workItem variable, the call to QueueBackgroundWorkItem would be ambiguous. Therefore, the assignment (sadly) isn't redundant.

The issue is that the C# compiler utilizes overload resolution when choosing a method from a method group, and overload resolution doesn't take into account return types. Since both Action and Func accept a single parameter of type CancellationToken, there's no way to distinguish the two overloads.

For more details, make sure to read this great StackOverflow answer from Eric Lippert Himself.

Leave a comment

Marius Schulz on Twitter Marius Schulz on GitHub RSS Feed