繁体   English   中英

如何将InstancePerLifetimeScope对象注入到ASP.NET之外的SingleInstance对象中?

[英]How can I inject an InstancePerLifetimeScope object into a SingleInstance object outside of ASP.NET?

我有一个使用Autofac的控制台应用程序。 该应用程序接受来自AMQP队列的消息并对其进行填充。 每次收到消息时,应用程序都会创建一个新的Autofac生存期范围并在其中注册一些内容。 值得注意的是,一个包含有关消息和触发消息的用户的信息的类(我们称其为Context )。 大多数实际功能(具有令人讨厌的大依赖关系树的一组服务类)在根Autofac范围中注册为InstancePerLifetimeScope 这会导致大量的内存分配和一些延迟,因为每次接收到消息时都会重新实例化所有这些类。

我想将大多数服务类转换为SingleInstance依赖项,因为从概念上讲它们只是功能的容器,并且没有注入依赖项的状态。 Autofac不允许我这样做,因为我要注入的Context对象是InstancePerLifetimeScope 很好,由于明显的原因,它不起作用(上下文在收到一条消息后会过时,并且Autofac无法在不重新实例化整个对象的情况下重新注入它)。

但是,即使我注入Func<Context>ContextProvider对象,情况也是如此。 我知道这是因为Autofac仍在寻找应该在我注册SingleInstance类的根范围中返回的Context ,而不是在当前生命周期范围内返回的Context

因此, 有没有办法让Autofac注入一个执行类似() => GetCurrentInnermostLifetimeScope().Resolve<Context>()的函数?

我还看到了一些其他的SO问题,但是它们都分为两类:“将所有内容注册为InstancePerLifetimeScope ”(我试图避免),或者“使用ASP.NET的DependencyResolver.Current ”(在控制台应用程序中不重要)。

有几种方法可以做到这一点,但是如果不超过您当前的做法,它们可能会招致同样多的费用。 在尝试使所有内容成为单例之前,我建议进行概要分析并找到实际上会影响性能的内容。 这种请求听起来像是过早的优化,并且也突破了一些明确定义的模式。

我想将大多数这些服务类转换为SingleInstance依赖项,因为从概念上讲它们只是功能的容器,并且没有注入依赖项的状态。

那意味着有状态。 为什么它们中没有任何其他状态很重要? 例如,它们的依赖项之一可能具有状态,因此必须将父项的范围也保持为每个请求的范围。 生命周期作用域可以帮助您确定可以拥有单身人士的图层,而您似乎没有。

话虽这么说,但是有很多解决办法。 我将给出一个示例,该示例类似于ASP.NET Core通过单例提供对HttpContext访问的方式。 假设您正在使用基于Task的处理; 如果没有,那么这将无济于事,但可以进行调整。 必须提供上下文,并且我将使用AsyncLocal<T> 如果每个请求都有自己的线程,则可以尝试[ThreadStatic]

using Autofac;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace InstanceRequest
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var builder = new ContainerBuilder();

            builder.RegisterType<ScopeAccessor>()
                .AsSelf()
                .As<IResolver>()
                .SingleInstance();

            builder.RegisterType<SingleInstance>()
                .SingleInstance();

            builder.RegisterType<IdAccessor>()
                .InstancePerLifetimeScope();

            builder.RegisterType<Request>()
                .InstancePerLifetimeScope();

            using (var container = builder.Build())
            {
                await RunAsync(container);
            }
        }

        private static async Task RunAsync(IContainer container)
        {
            var accessor = container.Resolve<ScopeAccessor>();

            var tasks = Enumerable.Range(0, 100).Select(async id =>
            {
                using (var scope = container.BeginLifetimeScope())
                {
                    accessor.CurrentScope = scope;

                    await scope.Resolve<Request>().RunAsync();
                }
            });

            await Task.WhenAll(tasks);
        }
    }

    class Request
    {
        private readonly IdAccessor _id;
        private readonly SingleInstance _s;

        public Request(IdAccessor id, SingleInstance s)
        {
            _id = id;
            _s = s;
        }

        public async Task RunAsync()
        {
            while (true)
            {
                var fromScoped = _id.Id;
                var fromSingleton = _s.Id;

                if (fromScoped != fromSingleton)
                {
                    throw new InvalidOperationException();
                }

                Console.WriteLine($"{fromScoped} == {fromSingleton}");

                await Task.Delay(100);
            }
        }
    }

    class SingleInstance
    {
        private readonly IResolver _resolver;

        public SingleInstance(IResolver resolver)
        {
            _resolver = resolver;
        }

        public int Id => _resolver.Resolve<IdAccessor>().Id;
    }

    interface IResolver
    {
        T Resolve<T>();
    }

    class ScopeAccessor : IResolver
    {
        private readonly AsyncLocal<ILifetimeScope> _scope = new AsyncLocal<ILifetimeScope>();

        public ILifetimeScope CurrentScope
        {
            get => _scope.Value;
            set => _scope.Value = value;
        }

        public T Resolve<T>() => CurrentScope.Resolve<T>();
    }

    class IdAccessor
    {
        private static int _id = 0;

        public IdAccessor()
        {
            Id = Interlocked.Increment(ref _id);
        }

        public int Id { get; }
    }
}

但是,我要指出的是,使用AsyncLocal<T>[ThreadStatic]也会产生不小的成本,并且可能比您认为遇到的GC压力还要大。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM