7

In ASP.NET Core, I am using IHttpContextAccessor to access the current HttpContext. HttpContextAccessor uses AsyncLocal<T>.

In one situation, I am trying to start a Task from within a request, that should continue running after the request has ended (long running, background task), and I am trying to detach it from the current execution context so that IHttpContextAccessor returns null (otherwise I am accessing an invalid HttpContext).

I tried Task.Run, Thread.Start, but every time, the context seems to carry over and IHttpContextAccessor returns the old context whatever I try (sometimes even contexts from other requests ).

How can I start a task that will have a clean AsyncLocal state so that IHttpContextAccessor returns null?

Flavien
  • 5,647
  • 7
  • 34
  • 45
  • 1
    You should avoid running/spawning your own threads/background tasks in ASP.NET Core anyways, it will mess up in the way how ASP.NET Core handles threads for incoming requests. You should do it properly with a kind of local or remote message bus and have real background processes (i.e. console application that do not host asp.net core) process it – Tseng Jun 26 '17 at 11:43
  • 2
    At the very least I should be able to create my own thread without interfering with Kestrel's request handling. Even that doesn't work, and captures some random ``HttpContext``. – Flavien Jun 26 '17 at 13:11
  • `Task.Run(...)`, etc. has a parameter where you can set the scheduler. You could set the default scheduler in here (ASP.NET Core runs its own scheduler), i.e. `Task.Run(() => DoSomething(), TaskScheduler.Default)`. But again, I **do not recommend you** doing that and you should consider a real background process like described above, because threads taken away for background tasks are threads which can't be used to accept request. ASP.NET Core should just use `await`/`async` for true async operation, not CPU intensive stuff or background tasks – Tseng Jun 26 '17 at 13:17
  • Also useful resource https://www.hanselman.com/blog/HowToRunBackgroundTasksInASPNET.aspx http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/, while it's mostly about ASP.NET 4, it's concepts still apply – Tseng Jun 26 '17 at 13:20
  • That's not true, ASP.NET Core uses threads from the thread pool to process requests. New threads (``var t = new Thread(...)``) are **not taken from the thread pool**, so they will not interfere with request handling. – Flavien Jun 26 '17 at 13:34
  • https://msdn.microsoft.com/en-us/magazine/dn802603.aspx _As a general rule, don’t queue work to the thread pool on ASP.NET._ And it is still valid for ASP.NET Core. Also `Task.Run` **will** take away a thread from the requestpool, since it's run with the **current** context's scheduler, which is ASP.NET Core's scheduler. Most important argument though is, you have **no guarantees** it will ever complete (i.e. IIS could shut down your ASP.NET Core process and with it your background tasks at any time for any reason) – Tseng Jun 26 '17 at 13:56
  • 1
    I'm not using IIS. Unlike IIS, Kestrel doesn't recycle its AppDomains (in fact, it doesn't have AppDomains at all). The background task being interrupted is not a concern in my use case anyway. And again, ``Thread.StartNew`` spawns a completely new thread which does not come from the thread pool, so your previous comment is beside the point. – Flavien Jun 26 '17 at 14:06

2 Answers2

8

Late answer but might be useful for someone else. You can do this by suppressing the ExecutionContext flow as you submit your task:

        ExecutionContext.SuppressFlow();

        var t = Task.Run(() =>
        {
            await LongRunningMethodAsync();
        });

        ExecutionContext.RestoreFlow();

This will prevent the ExecutionContext being captured in the submitted task. Therefore it will have a 'clean' AsyncLocal state.

As above though I wouldn't do this in ASP.net if it's CPU intensive background work.

miroslav22
  • 81
  • 1
  • 3
1

You could await the long running task and set HttpContext to null inside await. It would be safe for all code outside the task.

[Fact]
public async Task AsyncLocalTest()
{
    _accessor = new HttpContextAccessor();

    _accessor.HttpContext = new DefaultHttpContext();

    await LongRunningTaskAsync();

    Assert.True(_accessor.HttpContext != null);
}

private async Task LongRunningTaskAsync()
{
    _accessor.HttpContext = null;
}

You could run a separate thread, it doesn't matter.

Task.Run(async () => await LongRunningTaskAsync());

Just make the task awaitable to cleanup HttpContext for the task's async context only.

Ilya Chumakov
  • 18,652
  • 6
  • 71
  • 107
  • Doesn't seem to work for me. Accessing ``httpContextAccessor.HttpContext`` from inside the long running task still returns a non-null HttpContext. Also, I can't await the long running task from the request as I want the request to complete now, irrespective of how long the long running tasks runs for. – Flavien Jun 26 '17 at 11:26
  • @Flavien, you could use `Task.Run` to start the task in separate thread, it doesn't matter. The point is to place `await` between the caller and the task. For example: `Task.Run(async () => await LongRunningTaskAsync());` – Ilya Chumakov Jun 26 '17 at 11:42
  • I tried that, but even with the ``await``, it doesn't seem to make a difference. The ``HttpContext`` is still present. – Flavien Jun 26 '17 at 11:48
  • Ok, this works as long as I reuse the same instance of HttpContextAccessor. – Flavien Jun 26 '17 at 15:52
  • @Flavien, yes, it sould be registered as singleton. – Ilya Chumakov Jun 26 '17 at 15:59