Using HttpContext in Async Task

2019-01-24 03:59发布

问题:

I have the following mvc action.

public async Task<JsonResult> DoSomeLongRunningOperation()
{
    return await Task.Run(() =>
    {
        //Do a lot of long running stuff
        //The underlying framework uses the HttpContext.Current.User.Identity.Name so the user is passed on the messagebus.
    }
}

In the task the HttpContext gets null. We did a lot of tricking, but nothing assures us of the HttpContext being available always in our new thread.

Is there a solution to use HttpContext within out async tasks?

In our IocContainer we have registered the following object which passes the username to the framework.

public class HttpContextUserIdentityName : ICredentials
{
    public string Name
    {
        get { return HttpContext.Current.User.Identity.Name; }
    }
}

This code is called in a lot of places before persisting to the database.

We need either another way of getting the username of the user initiated the webrequest or fix the issue with the HttpContext being null.

Because the persisting to the database happens in the Task I can't access the HttpContext before entering the task.

I also can't think of a safe way to temporary persist the username so I can implement another ICredentials service object.

回答1:

You almost never want to use Task.Run in an ASP.NET method.

I think the cleanest solution (but the most work) is to implement async-compatible interfaces at your other layers:

public async Task<JsonResult> DoSomeLongRunningOperation()
{
  //Do a lot of long running stuff
  var intermediateResult = await DoLongRunningStuff();
  return await DetermineFinalResult(intermediateResult);
}


回答2:

I would try passing in the reference to the HttpContext as the state object, because that should create a new instance of that object on the stack for the thread that executes the work. Instead of using Task.Run, use

return await Task.Factory.StartNew((ctx) => 
{
    var context = (HttpContext)ctx;
   //Do stuff
}, httpContextObject);

Task.Run and Task.Factory.StartNew return immediately, so asp.net continues on in the event lifecycle in the worker thread that is handling the request while your thread is operating on the object that has already been disposed.



回答3:

You should get whatever information you need from the current context before you start the new thread. In this case, add something like:

string username = HttpContext.Current.User.Username;

before Task.Run and then use that inside of the other thread.

On a side note, as it stands, there's no reason to await the task. You can just return the task directly and not mark the method as Async.

If you need to access the Response object, which will presumably to utilize the results of the long running operation and thus can't be before Task.Run you should do so after the Task.Run (but ensure that the task is awaited). If you end up doing this then you can't do what I suggested in my previous paragraph.