首页 > 编程语言 >ASP.NET Core - 依赖注入(三)

ASP.NET Core - 依赖注入(三)

时间:2023-02-28 09:55:24浏览次数:55  
标签:Core ASP Console serviceProvider 生命周期 WriteLine var NET public

4. 容器中的服务创建与释放

我们使用了 IoC 容器之后,服务实例的创建和销毁的工作就交给了容器去处理,前面也讲到了服务的生命周期,那三种生命周期中对象的创建和销毁分别在什么时候呢。以下面的例子演示以下:

首先是新增三个类,用于注册三种不同的生命周期:

public class Service1
{
    public Service1()
    {
        Console.WriteLine("Service1 Created");
    }
}
public class Service2
{
    public Service2()
    {
        Console.WriteLine("Service2 Created");
    }
}
public class Service3
{
    public Service3()
    {
        Console.WriteLine("Service3 Created");
    }
}

接下来是演示场景,为了简单起见,就用后台服务程序吧

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
        services.AddSingleton<Service1>();
        services.AddScoped<Service2>();
        services.AddTransient<Service3>();
    })
    .Build();

await host.RunAsync();

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IServiceProvider _serviceProvid
    public Worker(ILogger<Worker> logger, IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        #region 生命周期实例创建
        Console.WriteLine("Service1 第一次调用");
        var service11 = _serviceProvider.GetService<Service1>();
        Console.WriteLine("Service1 第二次调用");
        var service12 = _serviceProvider.GetService<Service1>();

        // 创建作用域,与 Web 应用中的一次请求一样
        using (var scope = _serviceProvider.CreateScope())
        {
            Console.WriteLine("Service2 第一次调用");
            var service31 = scope.ServiceProvider.GetService<Service2>();
            Console.WriteLine("Service2 第二次调用");
            var service32 = scope.ServiceProvider.GetService<Service2>();

            using (var scope1 = _serviceProvider.CreateScope())
            {
                Console.WriteLine("Service2 第三次调用");
                var service33 = scope1.ServiceProvider.GetService<Service2>();
            }
        }
        {
            Console.WriteLine("Service3 第一次调用");
            var service41 = _serviceProvider.GetService<Service3>();

            Console.WriteLine("Service3 第二次调用");
            var service42 = _serviceProvider.GetService<Service3>();
            }
            #endregion
        }
    }
}

最终的输出如下:

image

通过输出,我们可以单例生命周期服务在第一次使用的时候创建,之后一直是一个实例,作用域生命周期服务在一个作用域中第一次使用的时候创建实例,之后在同一个实例中只保持一个,但在其他作用域中则会重新创建,而瞬时生命周期服务每次都会创建一个新实例。

看完创建,我们再看实例销毁的时机。

若服务实现了IDisposable接口,并且该服务是由DI容器创建的,则我们不应该手动去Dispose,DI容器会对服务自动进行释放。这里由两个关键点,一个是要实现 Idisposable 接口,一个是由容器创建。这里再增加多两个类,用于演示,并且为了避免干扰将之前演示创建过程的代码注释。

public class Service1 : IDisposable
{
    public Service1()
    {
        Console.WriteLine("Service1 Created");
  
    public void Dispose()
    {
        Console.WriteLine("Service1 Dispose");
    }
}

public class Service2 : IDisposable
{
    public Service2()
    {
        Console.WriteLine("Service2 Created");

    public void Dispose()
    {
        Console.WriteLine("Service2 Dispose");
    }
}

public class Service3 : IDisposable
{
    public Service3()
    {
        Console.WriteLine("Service3 Created");
    }

    public void Dispose()
    {
        Console.WriteLine("Service3 Dispose");
    }
}

public class Service4 : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Service4 Dispose");
    }
}

public class Service5 : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Service5 Dispose");
    }
}

之后后台服务程序也做一些修改

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
        services.AddSingleton<Service1>();
        services.AddScoped<Service2>();
        services.AddTransient<Service3>();
        // 这种方式依旧由容器创建实例,只不过提供了工厂方法
        services.AddSingleton<Service4>(provider => new Service4());
        // 这种方式是用外部创建实例,只有单例生命周期可用
        services.AddSingleton<Service5>(new Service5());
    })
    .Build();

await host.RunAsync();

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger;
    private readonly IServiceProvider _serviceProvid
    public Worker(ILogger<Worker> logger, IServiceProvider serviceProvider)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        #region 生命周期实
        Console.WriteLine("Service1 调用");
        var service1 = _serviceProvider.GetService<Service1>();

        // 创建作用域,与 Web 应用中的一次请求一样
        using (var scope = _serviceProvider.CreateScope())
        {
            Console.WriteLine("Service2 调用");
            var service2 = scope.ServiceProvider.GetService<Service2>();
            Console.WriteLine("即将结束作用域
            Console.WriteLine("Service3 调用");
            var service3 = scope.ServiceProvider.GetService<Service3>();
        }

        Console.WriteLine("Service4 调用");
        var service4 = _serviceProvider.GetService<Service4>();
        Console.WriteLine("Service5 调用");
        var service5 = _serviceProvider.GetService<Service5>();

        #endregion
    }
}

这样要直接用命令启动应用,不能够通过vs调试,之后Ctrl+C停止应用的时候,输出如下:

image

通过输出可以看得到,瞬时生命周期服务和作用域生命周期服务在超出作用范围就会被释放,而单例生命周期服务则在应用关闭时才释放,同为单例生命周期的Service5没有被释放。

这里要提一下的是,在解析瞬时生命周期服务Service3的时候,示例代码中是放到一个单独的作用域中的,这是因为在通过 services.AddHostedService<Worker>(); 注入Worker的时候是注入为单例生命周期的,而在单例生命周期对象中解析其他生命周期的对象是会有问题的,这也是服务注入、解析需要注意的一个关键点。

一定要注意服务解析范围,不要在 Singleton 中解析 Transient 或 Scoped 服务,这可能导致服务状态错误(如导致服务实例生命周期提升为单例,因为单例生命周期的服务对象只会在应用停止的时候释放,而单例对象都没释放,它的依赖项肯定不会释放)。允许的方式有:

  • 在 Scoped 或 Transient 服务中解析 Singleton 服务
  • 在 Scoped 或 Transient 服务中解析 Scoped 服务(不能和前面的Scoped服务相同)

如果要在单例生命周期示例中临时解析作用域、瞬时生命周期的服务,可以通过创建一个子作用域的方式。对子作用域 IServiceScope 的工作方式感兴趣的,可阅读一下这篇文章:细聊.Net Core中IServiceScope的工作方式



参考文章:
ASP.NET Core 依赖注入 | Microsoft Learn
理解ASP.NET Core - 依赖注入(Dependency Injection)



ASP.NET Core 系列:

目录:ASP.NET Core 系列总结
上一篇:ASP.NET Core - 依赖注入(二)

标签:Core,ASP,Console,serviceProvider,生命周期,WriteLine,var,NET,public
From: https://www.cnblogs.com/wewant/p/17110701.html

相关文章

  • Kubernetes部署的10个注意事项
    摘要容器开发有望在云中实现前所未有的可移植性和可扩展性。此外,DevOps开发和文化实践也有助于提升业务价值和响应能力。但是,在开始第一个容器开发项目之前,有一些问题......
  • .NET周报 【2月第4期 2023-02-25】
    国内文章.NET微服务系统迁移至.NET6.0的故事https://www.cnblogs.com/InCerry/p/microservice-migration-net-6.html本次迁移涉及的是公司内部一个业务子系统,该系统是一......
  • 困扰多年的 Kubernetes 健康检查问题终于解决了
    我们有一个部署在k8s上的应用被一个健康检查问题困扰多年,每次发布时由于健康检查(readinessprobe)失败,pod要被重启很多次,快的话要等半小时到1小时pod才能启动成功,慢......
  • Gateway集成Netty服务
    目录一、Netty简介二、Netty入门案例1、服务端启动2、通道初始化3、自定义处理器4、测试请求三、Gateway集成1、依赖层级2、自动化配置四、配置加载1、基础配置2、属性配置......
  • 【译】.NET 7 中的性能改进(七)
    原文|StephenToub翻译|郑子铭Arm64在.NET7中,大量的努力用于使Arm64的代码生成与x64的代码生成一样好或更好。我已经讨论了一些与架构无关的PR,还有一些是专门针对A......
  • 10. Kubernetes - DNS
    服务发现通过Service知道了后端的Pod服务可以通过ClusterIP代理出来让其他服务能够访问到。但也存在一个问题,Service可能会被更新或者重建,下一次的IP可能就变了......
  • .NET 5 的新功能 What‘s new in .NET 5
    本文内容​​.NET5.0doesn'treplace.NETFramework​​​​.NET5.0doesn'treplace.NETStandard​​​​C#updates​​​​F#updates​​​​VisualBasicupdate......
  • java.net.URL getHost因#@同时存在时优先级问题导致的SSRF
    #作为锚点,后面的会忽略;@作为登录信息,前面的会忽略对于java.net.URL取主机getHost()函数,当#@同时存在时,高版本#优先,取#前的;低版本@优先,取@后的。从而导致低版本(具体各......
  • #yyds干货盘点#【愚公系列】2023年02月 .NET/C#知识点-程序运行计时的总结
    前言在分析一个程序算法时间复杂度时,可以使用统计程序或程序片段的计算时间有助于理解程序性质,许多语言或系统都提供了内部计时功能。下面主要是讲解C#中的计时方式:Stop......
  • K8s学习(二)Kubernetest的资源管理及五大资源介绍
    前言本文是k8s学习系列文章,前后串起来是一个完整的课程(学习笔记),本文记录k8s的资源管理方式及五大类资源的具体管理命令,看完本文基本上就能实现k8s的基本操作,可以独立部署小......