资源生存期控制问题

时间:2014-01-15 15:29:47

标签: dependency-injection autofac

我正在尝试找到一种方法来管理资源的生命周期,而不依赖于使用它的组件。以下示例显示了我的方法。它还没有像预期的那样工作,但它表明了我的意图。

class GlobalService
{
    static int instanceCounter = 0;
    public int instanceId = ++instanceCounter;
    public void DoWhatever() { }
}

class Consumer
{
    private readonly Func<Owned<GlobalService>> _globalServiceFactory;
    public Consumer(Func<Owned<GlobalService>> globalServiceFactory)
    {
        _globalServiceFactory = globalServiceFactory;
    }

    public void UseService()
    {
        using (var service = _globalServiceFactory())
        {
            service.Value.DoWhatever();
            Console.WriteLine("service.instanceId = {0}\n", service.Value.instanceId);
        }
    }
}

class Program
{
    static ILifetimeScope rootScope;
    static ILifetimeScope globalServiceScope;
    static ILifetimeScope consumerScope;

    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GlobalService>().InstancePerMatchingLifetimeScope("globalServiceScope");
        builder.RegisterType<Consumer>().InstancePerMatchingLifetimeScope("consumerScope");

        var container = builder.Build();
        this.rootScope = container.BeginLifetimeScope();
        this.globalServiceScope = rootScope.BeginLifetimeScope("globalServiceScope");
        this.consumerScope = globalServiceScope.BeginLifetimeScope("consumerScope");

        var consumer = consumerScope.Resolve<Consumer>();
        consumer.UseService(); // service.instanceId is 1
        ResetServiceScope();
        consumer.UseService(); // service.instanceId is 1, but I want it to be 2
    }

    static void ResetServiceScope()
    {
        globalServiceScope.Dispose();
        globalServiceScope = rootScope.BeginLifetimeScope("globalServiceScope");
    }
}

我希望实现Consumer中的工厂始终返回当前活动的GlobalService实例,其实例在其他地方被控制,以便Consumer不需要知道任何有关如何GlobalService已重新加载。

我不确定我是否真的误用了Autofac(或一般的DI)因为我刚开始使用它(和一般的DI)。无论我是否滥用它,我现在都会被困在这里,并且非常感谢有人指出我正确的方向。

2 个答案:

答案 0 :(得分:0)

我很确定你是在滥用LifetimeScopes。由于consumerScope是从globalServiceScope创建的,因此当您处置globalServiceScope时,您也会处置consumerScope。从技术上讲,任何从这两个范围解决的服务都不应再被访问。例如,如果Consumer已实施IDisposable,则会在您处置globalServiceScope时将其处理。

我不确定你到底想要做什么,所以我不能建议一个更好的方法。

编辑:所以亚历山大指出,当外部容器处理时,我错误的是内部容器被丢弃,但这是因为一个已知的错误,没有简单的修复,不是因为它是有效的行为。如果有人修复了该错误,则上述用法会中断。

编辑2:我调整了你的代码以引入一个ReloadableSingleton类,因为我认为这就是你想要实现的目标。我添加了一些Debug.Assert代码只是为了验证它是否有效。显然,你可以把它拿回来。如果你使用Func<>接口,你可以消除Consumer中的IGlobalService要求,并注册了一个将每个方法调用推迟到单例的实现,但我不想编写所有这些,以防万一偏离轨道。这有帮助吗?

using System;
using System.Diagnostics;

using Autofac;
using Autofac.Builder;

namespace Demo
{
    internal class GlobalService
    {
        private static int instanceCounter = 0;
        public int instanceId = ++instanceCounter;
        public void DoWhatever() {}
    }

    internal class Consumer
    {
        private readonly Func<GlobalService> _globalServiceFactory;

        public Consumer(Func<GlobalService> globalServiceFactory)
        {
            _globalServiceFactory = globalServiceFactory;
        }

        public int UseService()
        {
            var service = _globalServiceFactory();
            service.DoWhatever();
            Console.WriteLine("service.instanceId = {0}\n", service.instanceId);
            return service.instanceId;
        }
    }

    internal class Program
    {
        private const string ConsumerTag = "consumerScope";
        private const string GlobalServiceTag = "globalServiceScope";

        private static ILifetimeScope rootScope;
        private static ILifetimeScope globalServiceScope;
        private static ILifetimeScope consumerScope;

        private static void Main()
        {
            var builder = new ContainerBuilder();

            // root scope:
            builder.RegisterReloadableSingleton<GlobalService>();

            // global service scope:
            builder.RegisterType<GlobalService>().InstancePerMatchingLifetimeScope(GlobalServiceTag);
            builder.RegisterSingletonReloader<GlobalService>().InstancePerMatchingLifetimeScope(GlobalServiceTag);

            // consumer scope:
            builder.RegisterReloadableSingletonAccessor<GlobalService>().InstancePerMatchingLifetimeScope(ConsumerTag);
            builder.RegisterType<Consumer>().InstancePerMatchingLifetimeScope(ConsumerTag);

            using (var container = builder.Build())
            using (rootScope = container.BeginLifetimeScope())
            {
                ReloadGlobalService();

                consumerScope = rootScope.BeginLifetimeScope(ConsumerTag);
                var consumer = consumerScope.Resolve<Consumer>();

                Debug.Assert(consumer.UseService() == 1, "1");

                ReloadGlobalService();
                Debug.Assert(consumer.UseService() == 2, "2");

                consumerScope.Dispose();
                globalServiceScope.Dispose();
            }
        }

        private static void ReloadGlobalService()
        {
            if (globalServiceScope != null)
                globalServiceScope.Dispose();

            globalServiceScope = rootScope.BeginLifetimeScope(GlobalServiceTag);
            globalServiceScope.ReloadSingleon<GlobalService>();
        }
    }

    internal class ReloadableSingleton<T>
    {
        public T Instance { get; set; }
    }

    internal static class ReloadableSingletonExtensions
    {
        public static void ReloadSingleon<T>(this IComponentContext scope)
        {
            scope.ResolveKeyed<Action>("reloader:" + typeof(T))();
        }

        public static IRegistrationBuilder<ReloadableSingleton<T>, SimpleActivatorData, SingleRegistrationStyle>
            RegisterReloadableSingleton<T>(this ContainerBuilder builder)
        {
            return builder.RegisterInstance(new ReloadableSingleton<T>()).SingleInstance();
        }

        public static IRegistrationBuilder<Func<T>, SimpleActivatorData, SingleRegistrationStyle>
            RegisterReloadableSingletonAccessor<T>(this ContainerBuilder builder)
        {
            return builder.Register<Func<T>>(c =>
            {
                var singleton = c.Resolve<ReloadableSingleton<T>>();
                // It's important to register the func and not the instance directly. Otherwise you'd get the original
                // instance every time, even after the Instance has been modified.
                return () => singleton.Instance;
            });
        }

        public static IRegistrationBuilder<Action, SimpleActivatorData, SingleRegistrationStyle>
            RegisterSingletonReloader<T>(this ContainerBuilder builder)
        {
            return builder.Register<Action>(c =>
            {
                var context = c.Resolve<IComponentContext>();
                return () =>
                {
                    var singleton = context.Resolve<ReloadableSingleton<T>>();
                    var newInstance = context.Resolve<T>();
                    singleton.Instance = newInstance;
                };
            })
                .Keyed<Action>("reloader:" + typeof (T));
        }
    }
}

答案 1 :(得分:0)

编辑:在ResetGlobalServiceInstance()中,删除了builder.Update(serviceContainer);并添加了由于内存泄漏而导致和创建新容器实例(Update()不会破坏之前的GlobalService实例)。

我只是找到了一个符合我需求的解决方案,并且不需要添加额外的管理类。所需要的只是两个单独的IContainer而不是一个。这是结果代码:

class GlobalService
{
    private static int instanceCounter = 0;
    public int instanceId = ++instanceCounter;
}

class Consumer
{
    private readonly Func<GlobalService> _globalServiceFactory;

    public Consumer(Func<GlobalService> globalServiceFactory)
    {
        _globalServiceFactory = globalServiceFactory;
    }

    public int UseService()
    {
        var service = _globalServiceFactory();
        return service.instanceId;
    }
}

class Program
{
    static IContainer consumerContainer;
    static IContainer serviceContainer;

    static void Main(string[] args)
    {
        var serviceBuilder = new ContainerBuilder();
        serviceBuilder.RegisterType<GlobalService>().SingleInstance();
        serviceContainer = serviceBuilder.Build();

        var consumerBuilder = new ContainerBuilder();
        consumerBuilder.RegisterType<Consumer>();
        consumerBuilder.Register(c => serviceContainer.Resolve<GlobalService>());
        consumerContainer = consumerBuilder.Build();

        var consumer = consumerContainer.Resolve<Consumer>();
        int serviceId = consumer.UseService();
        Debug.Assert(serviceId == 1, "Call 1, instance 1");
        serviceId = consumer.UseService();
        Debug.Assert(serviceId == 1, "Call 2, still instance 1");
        ResetGlobalServiceInstance();
        serviceId = consumer.UseService();
        Debug.Assert(serviceId == 2, "Call 3, now instance 2");
        serviceId = consumer.UseService();
        Debug.Assert(serviceId == 2, "Call 4, still instance 2");
    }

    public static void ResetGlobalServiceInstance()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<GlobalService>().SingleInstance();
        builder.Dispose();
        serviceContainer = builder.Build();
    }
}

虽然这解决了我现在的问题,但我很欣赏有关潜在问题的反馈,例如在一个应用程序中使用多个IContainer或者通常在此处进行重大的DI / Autofac滥用。