关闭ASP.NET Core应用程序时如何正确安全地处置容器中注册的单例实例 [英] How to correctly and safely dispose of singletons instances registered in the container when an ASP.NET Core app shuts down

查看:64
本文介绍了关闭ASP.NET Core应用程序时如何正确安全地处置容器中注册的单例实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找有关在我的ASP.NET Core 2.0应用程序关闭时如何正确安全地处置注册的单例实例的指南.

I am looking for guidance on how to correctly and safely dispose of registered singleton instances when my ASP.NET Core 2.0 app is shutting down.

根据以下文档,如果我注册一个单例实例(通过IServiceCollection),则容器将永远不会尝试创建实例(也不会处置该实例),因此当应用程序关闭.

According to the following document, if I register a singleton instance (via IServiceCollection) the container will never attempt to create an instance (nor will it dispose of the instance), thus I am left to dispose of these instances myself when the app shuts down.

https://docs .microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view = aspnetcore-2.0 (2.1的指导相同)

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.0 (2.1 has the same guidance)

我附上一些伪代码,说明我要实现的目标.

I enclose some pseudo code that illustrates what I am trying to achieve.

请注意,我必须维护对IServiceCollection的引用,因为提供给OnShutDown方法的IServiceProvider是一个简单的服务定位器,并且无法让我执行复杂的查询.

Note I am having to maintain a reference to IServiceCollection since the IServiceProvider provided to the OnShutDown method is a simple service locator and doesn't give me the ability to execute complex queries.

当应用程序关闭时,我想要一种通用的方式来确保所有单例实例都已处置.我可以直接维护对所有这些单例实例的引用,但这不能很好地扩展.

When the app shuts down I want a generic way to ensure all singleton instances are disposed. I could maintain a reference to all these singleton instances directly but this doesn't scale well.

我最初使用工厂方法来确保DI管理我的对象的生存期,但是工厂方法的执行是在运行时在处理请求的管道中进行的,这意味着如果引发异常,则响应是500 InternalServerError,并记录了一个错误.通过直接创建对象,我正在努力争取更快的反馈,以使启动时的错误导致部署期间的自动回滚.这对我来说似乎并不合理,但是与此同时,我也不要滥用DI.

I originally used the factory method which would ensure the DI managed the lifetime of my objects, however, the execution of the factory method happened at runtime in the pipeline of handling a request, which meant that if it threw an exception the response was 500 InternalServerError and an error was logged. By creating the object directly I am striving for faster feedback so that errors on startup lead to a automatic rollback during the deployment. This doesn't seem unreasonable to me, but then at the same time I don't to misuse the DI.

有人对我如何更好地实现这一目标有任何建议吗?

namespace MyApp
{
    public class Program
    {
        private static readonly CancellationTokenSource cts = new CancellationTokenSource();

        protected Program()
        {
        }

        public static int Main(string[] args)
        {
            Console.CancelKeyPress += OnExit;
            return RunHost(configuration).GetAwaiter().GetResult();
        }

        protected static void OnExit(object sender, ConsoleCancelEventArgs args)
        {
            cts.Cancel();
        }

        static async Task<int> RunHost()
        {
            await new WebHostBuilder()
                .UseStartup<Startup>()
                .Build()
                .RunAsync(cts.Token);
        }
    }

    public class Startup
    {
        public Startup()
        {
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // This has been massively simplified, the actual objects I construct on the commercial app I work on are
            // lot more complicated to construct and span several lines of code.
            services.AddSingleton<IDisposableSingletonInstance>(new DisposableSingletonInstance());

            // See the OnShutdown method below
            this.serviceCollection = services;
        }

        public void Configure(IApplicationBuilder app)
        {
            var applicationLifetime = app.ApplicationServices.GetRequiredService<IApplicationLifetime>();
            applicationLifetime.ApplicationStopping.Register(this.OnShutdown, app.ApplicationServices);

            app.UseAuthentication();
            app.UseMvc();
        }

        private void OnShutdown(object state)
        {
            var serviceProvider = (IServiceProvider)state;

            var disposables = this.serviceCollection
                .Where(s => s.Lifetime == ServiceLifetime.Singleton &&
                            s.ImplementationInstance != null &&
                            s.ServiceType.GetInterfaces().Contains(typeof(IDisposable)))
                .Select(s => s.ImplementationInstance as IDisposable).ToList();

            foreach (var disposable in disposables)
            {
                disposable?.Dispose();
            }
        }
    }
}

推荐答案

处理由它创建的任何IDisposable对象(无论是临时对象,作用域对象还是单例对象)是DI的工作. 不要注册现有的单例,除非您打算在以后清理它们.

It's the DI's job to dispose of any IDisposable objects it creates, whether transient, scoped or singleton. Don't register existing singletons unless you intend to clean them up afterwards.

在问题的代码中,没有理由注册DisposableSingletonInstance的实例.它应该向:

In the question's code there's no reason to register an instance of DisposableSingletonInstance. It should be registered with :

services.AddSingleton<IDisposableSingletonInstance,DisposableSingletonInstance>();

当处置IServiceCollection时,它将在其创建的所有一次性实体上调用Dispose().对于Web应用程序,当RunAsync()结束时会发生这种情况;

When the IServiceCollection gets disposed, it will call Dispose() on all the disposable entities created by it. For web applications, that happens when RunAsync() ends;

作用域服务也是如此.不过,在这种情况下,实例将在范围退出时(例如,请求结束时)被处置.

The same holds for scoped services. In this case though, the instances will be disposed when the scope exits, eg when a request ends.

ASP.NET为每个请求创建一个范围.如果您希望在该请求结束时处置您的服务,则应使用以下命令进行注册:

ASP.NET creates a scope for each request. If you want your service to be disposed when that request ends, you should register it with :

services.AddScoped<IDisposableSingletonInstance,DisposableSingletonInstance>();

验证

对于最新的

通过直接创建对象,我正在努力争取更快的反馈,以使启动时的错误导致部署期间的自动回滚.

By creating the object directly I am striving for faster feedback so that errors on startup lead to a automatic rollback during the deployment.

这是一个不同的问题.部署错误通常是由错误的配置值,无响应的数据库等引起的.

That's a different problem. Deployment errors are often caused by bad configuration values, unresponsive databases etc.

验证服务

一个非常快的&检查的肮脏方法是一旦所有启动步骤均完成后,立即实例化 :

A very quick & dirty way to check would be to instantiate the singleton once all startup steps are complete with :

services.GetRequiredService<IDisposableSingletonInstance>();

验证配置

验证配置更为复杂,但并非那么棘手.可以将配置类上的数据注释属性用于简单规则,并使用

Validating the configuration is more involved but not that tricky. One could use Data Annotation attributes on the configuration classes for simple rules and use the Validator class to validate them.

另一种选择是使用必须由每个配置类实现的Validate方法创建IValidateable接口.这使使用反射的发现变得容易.

Another option is to create an IValidateable interface with a Validate method that has to be implemented by each configuration class. This makes discovery easy using reflection.

这篇文章显示IValidator界面如何与IStartupFilter结合使用,以在应用程序首次启动时验证所有配置对象

This article shows how the IValidator interface can be used in conjunction with an IStartupFilter to validate all configuration objects when an application starts for the first time

摘自文章:

public class SettingValidationStartupFilter : IStartupFilter  
{
    readonly IEnumerable<IValidatable> _validatableObjects;
    public SettingValidationStartupFilter(IEnumerable<IValidatable> validatableObjects)
    {
        _validatableObjects = validatableObjects;
    }

    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        foreach (var validatableObject in _validatableObjects)
        {
            validatableObject.Validate();
        }

        //don't alter the configuration
        return next;
    }
}

构造函数从DI提供程序获取所有实现IValidatable的实例,并在其上调用Validate()

The constructor gets all instances that implement IValidatable from the DI provider and calls Validate() on them

这篇关于关闭ASP.NET Core应用程序时如何正确安全地处置容器中注册的单例实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆