使用AsyncLocal实现单例模式 vs 范围服务

8

我研究了一下在.NET Core中如何创建HttpContext。然后我发现有一个叫做HttpContextFactory的类,它创建并将HttpContext对象分配给HttpContextAccessor类的HttpContext属性。为了在我们的代码中使用HttpContext对象,我们需要将IHttpContextAccessor注入到需要该对象的类的构造函数中。

当我查看HttpContextAccessor的实现时,显然它的HttpContext属性从一个私有的AsyncLocal变量获取HttpContext对象的值,然后HttpContextAccessor被注册为Singleton

https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/HttpContextAccessor.cs

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Threading;

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

        public HttpContext HttpContext
        {
            get
            {
                return  _httpContextCurrent.Value?.Context;
            }
            set
            {
                var holder = _httpContextCurrent.Value;
                if (holder != null)
                {
                    // Clear current HttpContext trapped in the AsyncLocals, as its done.
                    holder.Context = null;
                }

                if (value != null)
                {
                    // Use an object indirection to hold the HttpContext in the AsyncLocal,
                    // so it can be cleared in all ExecutionContexts when its cleared.
                    _httpContextCurrent.Value = new HttpContextHolder { Context = value };
                }
            }
        }

        private class HttpContextHolder
        {
            public HttpContext Context;
        }
    }
}

我很好奇,这种做法相比使用 Scope 服务的好处是什么?在我的看来,两者都可以使对象在请求范围内可用。

如果是作用域服务,我想HttpContextAccessor会像这样:

using System.Threading;

namespace Microsoft.AspNetCore.Http
{
    public class HttpContextAccessor : IHttpContextAccessor
    {
        private HttpContextHolder _httpContextCurrent;

        public HttpContext HttpContext
        {
            get
            {
                return  _httpContextCurrent?.Context;
            }
            set
            {
                if (value != null)
                {
                    _httpContextCurrent = new HttpContextHolder { Context = value };
                }
            }
        }

        private class HttpContextHolder
        {
            public HttpContext Context;
        }
    }
}

然后将其用作作用域服务
services.TryAddScope<IHttpContextAccessor, HttpContextAccessor>();

我希望了解每种方法的优缺点,以便在为我的项目创建库时理解何时使用Singleton with AsyncLocal或Scope。
2个回答

3
只要是单例模式,解决的IHttpContextAccessor实例可以被一个单例服务永久地持有并正常工作,但如果一个单例服务解决了一个作用域IHttpContextAccessor,可能会导致问题。请注意保留HTML标记。

我认为你误解了我的问题,如果你看一下这个文件 https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http/src/HttpServiceCollectionExtensions.cs。`IHttpContextAccessor`被注册为单例。这是因为他们使用`AsyncLocal`将HttpContext对象存储在`HttpContextAccessor`类中。我想知道的是,如果将IHttpContextAccessor注册为作用域,并且不使用AsyncLocal将HttpContext存储在HttpContextAccessor类中,会有什么区别。 - muhihsan
DefaultHttpContextFactory 本身并没有注册为 Singleton,而是以 Transient 的方式注册。您可以在此文件中查看:https://github.com/aspnet/AspNetCore/blob/f3f9a1cdbcd06b298035b523732b9f45b1408461/src/Hosting/Hosting/src/WebHostBuilder.cs#L284。该类负责设置 HttpContext 的值。 - muhihsan
如果你的意思是,这个类被设置为单例模式,这样每当我们在项目中创建自己的单例类时,我们就可以在那里注入 IHttpContextAccessor。这有点说得通 :) 但我能问一下,在我们的 Web 项目中何时需要从单例类访问 IHttpContextAccessor 的场景吗? - muhihsan
对于一些具有状态的服务,可能存在必须在请求内调用的方法(这意味着对象具有单例生命周期,而方法调用则是作用域限定的),因此将其作为一个服务而不是依赖于单例存储服务的作用域服务可能会更加有用。 - Alsein
@Alsien,这可能是因为在ASP.NET Core中,IServiceProvider不允许在单例中注入作用域服务,所以HttpContextAccessor被设计成使用AsyncLocal实现单例而不是Scoped。 - GPuri

2

我猜其中一个原因可能是 Asp.Net Core 的 IServiceProvider 不允许在 Singleton 类中注入 Scoped 依赖项。这可能是背后的主要决定。如果按照您建议的方式进行作用域,则使用它的所有类都可能需要进行作用域范围限制。但有趣的是,一旦请求被服务处理完毕,HTTPContext 就变为 null。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接