As you might’ve noticed, keeping threads running after a request returns, for processing post operational tasks (such as performing analytics on a file that was uploaded, etc) don’t always complete in a web project. There are several issues with spawning threads in the context of an ASP.NET project. Phil Haack’s post explains the issues in more detail. The following classes solve the problem of IIS killing threads before they complete. First part is the IISTaskManager:
using NLog; using System; using System.Threading.Tasks; using System.Web.Hosting; namespace Web.Models { /// /// Static class for running background tasks in IIS. /// Any spawned threads are registered properly with IIS /// to ensure they finish execution. /// public static class IISTaskManager { /// /// Runs a background task that is registered with the hosting environment /// so it is guaranteed to finish executing. /// /// The lambda expression to invoke. public static void Run(Action action) { new IISBackgroundTask().DoWork(action); } /// /// Generic object for completing tasks in a background thread /// when the request doesn't need to wait for the results /// in the response. /// class IISBackgroundTask : IRegisteredObject { /// /// Constructs the object and registers itself with the hosting environment. /// public IISBackgroundTask() { HostingEnvironment.RegisterObject(this); } /// /// Called by IIS, once with set to false /// and then again with set to true. /// void IRegisteredObject.Stop(bool immediate) { if (_task.IsCompleted || _task.IsCanceled || _task.IsFaulted || immediate) { // Task has completed or was asked to stop immediately, // so tell the hosting environment that all work is done. HostingEnvironment.UnregisterObject(this); } } /// /// Invokes the as a Task. /// Any exceptions are logged /// /// The lambda expression to invoke. public void DoWork(Action action) { try { _task = Task.Run(action); } catch (AggregateException ex) { // Log exceptions foreach (var innerEx in ex.InnerExceptions) { _logger.ErrorException(innerEx.ToString(), innerEx); } } catch (Exception ex) { _logger.ErrorException(ex.ToString(), ex); } } private Task _task; private static Logger _logger = LogManager.GetCurrentClassLogger(); } } }
IISTaskManager.Run(() => { //Run any piece of code which needs to run in a new thread } );
Quick Links
Legal Stuff