Jeremy Holovacs Jeremy Holovacs - 4 months ago 47
C# Question

ASP.NET ThreadPool.QueueUserWorkItem Common.Logging to NLog crashes IIS - Bug or just me?

I have a long-running asynchronous task that is kicked off from an ASP.NET MVC4 web page. The controller method looks like this:

public ActionResult Index(IndexModel model)
if (ModelState.IsValid)
model.NotificationRecipient = model.NotificationRecipient.Replace(';', ',');
ImportConfiguration config = new ImportConfiguration()
BatchId = model.BatchId,
ReportRecipients = model.NotificationRecipient.Split(',').Select(c => c.Trim())
System.Threading.ThreadPool.QueueUserWorkItem(foo => LaunchFileImporter(config, this.HttpContext.ApplicationInstance.Context));
if (model.RunExport) ThreadPool.QueueUserWorkItem(foo => LaunchFileExporter());
Log.InfoFormat("Queued the ImportProcessor to process invoices. Send Notification: {0} Email Recipient: {1}",
model.SendNotification, model.NotificationRecipient);
TempData["message"] = "The import processor job has been started.";
//return RedirectToAction("Index", "Home");
catch (Exception ex)
Log.Error("Failed to properly queue the invoice import job.", ex);
ModelState.AddModelError("", ex.Message);

var dirInfo = new System.IO.DirectoryInfo(dir);
model.Files = dirInfo.EnumerateFiles("*.xml").OrderBy(x => x.Name.ToLower());

return View(model);

method looks like this:

private void LaunchFileImporter(ImportConfiguration config, System.Web.HttpContext context)
//the semaphore prevents concurrent running of this process, which can cause contention.
Log.Trace(t => t("submitter semaphore: {0}", (exporter == null) ? "NULL" : "present."));
Log.Trace(t => t("Context: {0}", context));
using (var processor = new ImportProcessor(context))
processor.OnFileProcessed += new InvoiceFileProcessing(InvoiceFileProcessingHandler);
processor.OnInvoiceProcessed += new InvoiceSubmitted(InvoiceSubmittedHandler);
catch (Exception ex)
Log.Error("Failed in execution of the File Importer.", ex);

My Logger is a Common.Logging
private static readonly ILog
, and is configured for NLog. It seems properly wired up; at least, I get a fair amount of logs out of it.

Here's the thing: The moment I hit
, the application pool death spirals into a silent death, resetting the app pool, reloading the membership provider, reprocessing the web.config, the whole shebang... No YSOD, no indication on the web page... everything just quietly blows up. The last log entry I get is
Queued the ImportProcessor to process invoices...

I should note the page does the refresh. The
is populated and displayed on the screen, which makes me believe the problem is happening in the asynchronous process... but pretty much immediately. Due to the lack of additional logs I am assuming there is a problem with the logger.

So I'm hoping someone can either tell me what is happening, point to some documented issue with this, tell me how I'm being an idiot, or reproduce something similar as a bug.



@RichardDeeming pointed out that the context information was not getting into the spawned thread, and this seemed to be the cause of the problem. I still haven't wrapped my brain around why this didn't work nor did it write the trace messages, but once I captured the part of the context that I needed, the
, and used that instead of the context object, it just worked.


You'll get a NullReferenceException in the line:

ThreadPool.QueueUserWorkItem(foo => LaunchFileImporter(config, HttpContext.ApplicationInstance.Context));

The HttpContext gets cleaned up once the request has completed. Since the exception is thrown on a background thread, it will bring down the whole AppDomain, causing your application to restart.

You need to capture the relevant state from the context in the controller action, and use that state in the WaitCallback delegate:

IPrincipal user = Context.User;
ThreadPool.QueueUserWorkItem(foo => LaunchFileImporter(config, user));

// Or:
// ThreadPool.QueueUserWorkItem(state => LaunchFileImporter(config, (IPrincipal)state);