I have a long-running asynchronous task that is kicked off from an ASP.NET MVC4 web page. The controller method looks like this:
[HttpPost]
public ActionResult Index(IndexModel model)
{
if (ModelState.IsValid)
{
try
{
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);
}
My LaunchFileImporter 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."));
submitter.WaitOne();
try
{
Log.Trace(t => t("Context: {0}", context));
using (var processor = new ImportProcessor(context))
{
processor.OnFileProcessed += new InvoiceFileProcessing(InvoiceFileProcessingHandler);
processor.OnInvoiceProcessed += new InvoiceSubmitted(InvoiceSubmittedHandler);
processor.Execute(config);
}
}
catch (Exception ex)
{
Log.Error("Failed in execution of the File Importer.", ex);
}
submitter.Release();
}
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 System.Threading.ThreadPool.QueueUserWorkItem, 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 TempData["message"] 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.
Thanks!
UPDATE
@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 IPrincipal, and used that instead of the context object, it just worked.
You’ll get a
NullReferenceExceptionin the line:The
HttpContextgets cleaned up once the request has completed. Since the exception is thrown on a background thread, it will bring down the wholeAppDomain, 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
WaitCallbackdelegate: