[C#].Net Core gets HttpContext.Current and AsyncLocal and ThreadLocal

Keywords: ASP.NET

In DotNet Core, the context of the current request is no longer available through HttpContext.Current, as in MVC5.

However, Microsoft provides an IHttpContext Accessor to allow us to access the Http context of the current request, its definition
As follows:

namespace Microsoft.AspNetCore.Http
{
    public interface IHttpContextAccessor
    {
        HttpContext HttpContext { get; set; }
    }
}

If you need to use it, you need to add it to the Ioc container. In the ConfigureService of the Startup class, we can register its default implementation in Ioc.

public void ConfigureService(IServiceCollection services)
{
    services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

So let's look at the implementation of HttpContextAccessor:

using System.Threading;

namespace Microsoft.AspNetCore.Http
{
    public class HttpContextAccessor : IHttpContextAccessor
    {
        private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();

        public HttpContext HttpContext
        {
            get
            {
                return _httpContextCurrent.Value;
            }
            set
            {
                _httpContextCurrent.Value = value;
            }
        }
    }
}

Inside, an AsyncLocal < HttpContext > is used to save an instance of HttpContext. When was Accessor assigned? The answer is to assign each HTTP request.

What is AsyncLocal < T >?
AsyncLocal < T > is an object introduced after. Net 4.6, which accepts a generic parameter. Its main function is to save the value of a variable shared in the asynchronous waiting context.
The asynchronous method is Task-based automatic thread scheduling, which may lead to data loss during asynchronous context switching. For example, a variable is assigned before the await call, which is shared among multiple threads. When the await call returns to the previous call point, the code after the call point may be on the previous thread, or it may be scheduled to other threads.

For instance:

static async Task TestMethod()
{
    Console.WriteLine($"Current thread ID{Thread.CurrentThread.ManagedThreadId}");
    await Task.Delay(100);
    Console.WriteLine($"Current thread ID{Thread.CurrentThread.ManagedThreadId}");
}

After await waits for the task to complete, the ID output from the later code is different from the ID before the call, indicating that thread switching has occurred:

static void Main(string[] args)
{
    Action @delegate = async () => await TestMethod();

    @delegate();
    Console.ReadKey();
}


From the code point of view, they seem to be on the same thread, but the thread switching operation happened at the time of execution.
And what happens here if we use a ThreadLocal < T > variable to store it?

static ThreadLocal<int> _threadLocal = new ThreadLocal<int>();
static AsyncLocal<int> _asyncLocal = new AsyncLocal<int>();

static void Main(string[] args)
{
    Action @delegate = async () => await TestMethod();

    @delegate();
    Console.ReadKey();
}

static async Task TestMethod()
{
    _threadLocal.Value = 1000;
    _asyncLocal.Value = 2000;
    Console.WriteLine($"Current thread ID{Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine($"{nameof(_threadLocal)},value:{_threadLocal.Value}");
    Console.WriteLine($"{nameof(_asyncLocal)},value:{_asyncLocal.Value}");
    await Task.Delay(100);
    Console.WriteLine($"Current thread ID{Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine($"{nameof(_threadLocal)},value:{_threadLocal.Value}");
    Console.WriteLine($"{nameof(_asyncLocal)},value:{_asyncLocal.Value}");
}


SO, as explained here, ThreadLocal is used to store different variable values for different threads, that is, the same variable can store different values in different threads. This is used here to ensure the uniqueness of variables in the TestMethod method, which is no problem in the synchronous method, but the use of await keyword causes the code to be dispatched to other threads after waiting for the asynchronous call to end, so it is useless here. AsyncLocal < T > is prepared for this situation.

Posted by dysonline on Tue, 01 Jan 2019 11:36:08 -0800