16

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.

tereško
  • 58,060
  • 25
  • 98
  • 150
Marco
  • 4,817
  • 5
  • 34
  • 75
  • See also http://stackoverflow.com/questions/8925227/access-httpcontext-current-from-threads – Rory Dec 30 '15 at 17:23

3 Answers3

5

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);
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Ofcourse that code is in different layer of the architecture, but that is not the problem here. I would still have the same issue if I would define that in different class. – Marco Apr 15 '19 at 15:00
  • Really bad answer. There are many APIs that require Task. – The Sharp Ninja Nov 10 '20 at 19:48
  • @TheSharpNinja: I'll stand by my answer. If you need a `Task`, then use `async`/`await`, not `Task.Run`. – Stephen Cleary Nov 11 '20 at 03:02
  • That's not possible if the function being called must return void. Using async void causes problems with exception handling. Using Task.Run is the correct technique in such cases. Now, I have been giving this a lot of thought and using a custom scheduler that associates threads to context objects would solve the problem. – The Sharp Ninja Nov 11 '20 at 04:42
  • @TheSharpNinja: Perhaps you could expound more on your scenario and post your own question? `Task.Run` does not sound like a good solution for that case. – Stephen Cleary Nov 11 '20 at 14:13
4

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • I updated my question... Need to access the context within the task or need another safe solution for getting my username before saving stuff to the database. – Marco Dec 06 '12 at 16:55
  • 1
    @MarcoFranssen You won't be able to access the current context from a background thread, it's just not going to happen. As I have already said, you need to get the information from the current context before starting the background thread. The other issue might be that you are doing more than you should be in the background task. Perhaps sections of this method should be refactored out entirely to run else where, or only a smaller section that actually interacts with the DB or does some processing should be set to run in a background task. The problem here is mixing business and UI code. – Servy Dec 06 '12 at 17:04
  • @Servy You can actually close over the HttpContext object as the state object for a new task. This should create a new copy of that object on the stack for the new thread. See answer below. – pwnyexpress Dec 06 '12 at 18:13
  • @hotSauce.Open I've listed several of the problems with that in a comment to your thread; that aside, you're not actually closing over it in your code, you're just passing it in as a formal parameter. That's...different. – Servy Dec 06 '12 at 18:18
3

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.

pwnyexpress
  • 1,016
  • 7
  • 14
  • 5
    First off, you're not creating a copy of the object, you're just copying *the reference to the object*. `HttpContext` isn't a `struct`. Next, there's a reason it's not accessible from a background thread, and that reason is that it was not designed to be accessed from other contexts. It's not a thread safe object by design, you can only rely on the current state of the object when you're in the appropriate sync context, etc. You're simply not *supposed* to access the `HttpContext` from another thread, in just the way that in a winform app you don't access controls from a non-UI thread. – Servy Dec 06 '12 at 18:17
  • @Servy According to [this similar question and answer](http://stackoverflow.com/a/10662486/1429122), you can close over the HttpContext. If each thread gets it's own stack and the HttpContext is passed to that thread as a parameter, then a copy of that object should get created on the heap for the new thread. – pwnyexpress Dec 06 '12 at 18:34
  • 5
    Sure you *can* close over it. I never said you *couldn't* I said you *shouldn't*. Yes, each thread gets it's own stack, I'm not disputing that. What I am disputing is that what you're doing is in any way copying the context; it's not. While the stacks differ (by necessity) the *heap* is shared. Since `HttpContext` is a reference type, the actual object sits on the heap. You are only copying *a reference to that object* in your closure. – Servy Dec 06 '12 at 18:50
  • Ok, this may not be the answer for the above question, but it was useful for me, thanks! :) – dandel Dec 17 '12 at 12:39