asp.net mvc 4.5: getting HostingEnvironment.QueueB

2019-08-14 09:25发布

问题:

I'm trying to finish some lightweight tasks after a controller has finished his work and I use HostingEnvironment.QueueBackgroundWorkItem() for this.

I'm seeing a strange behavior, so I make an artificial proof-of-concept app for this.

in my global.asax.cs I have this nice function:

public class MvcApplication : HttpApplication
{
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

    public static void ContinueWorkOnBackground(Task workItem)
    {
        workItem.ContinueWith(t =>
        {
            if (t.IsFaulted)
            {
                var ex = t.Exception;
                logger.Error(ex);
            }
        });
        HostingEnvironment.QueueBackgroundWorkItem(_ => workItem);
    }

And in my controller I create a workitem and throw there:

public ActionResult About()
{
    logger.Info("About");
    ViewBag.Message = "Your application description page.";
    MvcApplication.ContinueWorkOnBackground(TestWorkItem());
    return View();
}

private async Task TestWorkItem()
{
    logger.Trace("TestWorkItem");
    await Task.Delay(500);
    logger.Trace("let's fail");
    throw new NotImplementedException();
}

I see the message "TestWorkItem" in logs, but never "let's fail" and no message on error.

I also did HostingEnvironment.QueueBackgroundWorkItem((Func<CancellationToken,Task>)(_ => workItem)); to make sure the call is not ambiguous.

How do I fix this?

回答1:

Your problem is that TestWorkItem is capturing the current request context, and attempting to resume on that request context after its await. (I explain how this capture/resume works on my blog). Since the request has completed, the request context no longer exists by the time it attempts to resume.

The best fix for this is to allow QueueBackgroundWorkItem to execute the code outside of a request context. So, first change your helper method to take a Func<Task> instead of a Task:

public static void ContinueWorkOnBackground(Func<Task> workItem)
{
  HostingEnvironment.QueueBackgroundWorkItem(async _ =>
  {
    try
    {
      await workItem();
    }
    catch (Exception ex)
    {
      logger.Error(ex);
    }
  });
}

Then you can call it as such:

public ActionResult About()
{
  logger.Info("About");
  ViewBag.Message = "Your application description page.";
  MvcApplication.ContinueWorkOnBackground(() => TestWorkItem());
  return View();
}