草庐IT

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

wewant 2023-03-28 原文

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
        }
    }
}

最终的输出如下:

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

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

若服务实现了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停止应用的时候,输出如下:

通过输出可以看得到,瞬时生命周期服务和作用域生命周期服务在超出作用范围就会被释放,而单例生命周期服务则在应用关闭时才释放,同为单例生命周期的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 - 依赖注入(二)
下一篇:ASP.NET Core - 依赖注入(四)

有关ASP.NET Core - 依赖注入(三)的更多相关文章

  1. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  2. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  3. ruby - 这个 ruby​​ 注入(inject)魔术是如何工作的? - 2

    我今天看到了一个ruby​​代码片段。[1,2,3,4,5,6,7].inject(:+)=>28[1,2,3,4,5,6,7].inject(:*)=>5040这里的注入(inject)和之前看到的完全不一样,比如[1,2,3,4,5,6,7].inject{|sum,x|sum+x}请解释一下它是如何工作的? 最佳答案 没有魔法,符号(方法)只是可能的参数之一。这是来自文档:#enum.inject(initial,sym)=>obj#enum.inject(sym)=>obj#enum.inject(initial){|mem

  4. ruby - 在 ASP 页面上 Mechanize 中断 - 2

    require'mechanize'agent=Mechanize.newlogin=agent.get('http://www.schoolnet.ch/DE/HomeDE.htm')agent.clicklogin.link_withtext:/Login/然后我得到Mechanize::UnsupportedSchemeError。 最佳答案 Mechanize不支持javascript但您可以将搜索字段添加到表单并为其分配搜索词并使用mechanize提交表单form=page.forms.firstform.add_fie

  5. ruby - 有什么方法可以告诉 sidekiq 一项工作依赖于另一项工作 - 2

    有什么方法可以告诉sidekiq一项工作依赖于另一项工作,并且在后者完成之前无法开始? 最佳答案 仅使用Sidekiq;答案是否定的。正如DickieBoy所建议的那样,您应该能够在依赖作业完成时将其启动。像这样。#app/workers/hard_worker.rbclassHardWorkerincludeSidekiq::Workerdefperform()puts'Doinghardwork'LazyWorker.perform_async()endend#app/workers/lazy_worker.rbclassLaz

  6. ruby - 防止SQL注入(inject)/好的Ruby方法 - 2

    Ruby中防止SQL注入(inject)的好方法是什么? 最佳答案 直接使用ruby?使用准备好的语句:require'mysql'db=Mysql.new('localhost','user','password','database')statement=db.prepare"SELECT*FROMtableWHEREfield=?"statement.execute'value'statement.fetchstatement.close 关于ruby-防止SQL注入(inject

  7. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  8. Ruby:映射和注入(inject)之间的区别 - 2

    在此处阅读有关SO的各种解释,它们是这样描述的:map:Themapmethodtakesanenumerableobjectandablock,andrunstheblockforeachelement注入(inject):Injecttakesavalueandablock,anditrunsthatblockonceforeachelementofthelist.希望你明白为什么我觉得它们表面上看起来很相似。我什么时候会选择一个而不是另一个,它们之间有什么明显的区别吗? 最佳答案 如果您认为inject也别名为reduce,这

  9. ruby - 为什么 Ruby 注入(inject)方法不能对没有初始值的字符串长度求和? - 2

    为什么下面的代码会报错?['hello','stack','overflow'].inject{|memo,s|memo+s.length}TypeError:can'tconvertFixnumintoStringfrom(irb):2:in`+'from(irb):2:in`blockinirb_binding'from(irb):2:in`each'from(irb):2:in`inject'from(irb):2如果传递了初始值,它就可以正常工作:['hello','stack','overflow'].inject(0){|memo,s|memo+s.length}=>18

  10. ruby - 在多个线程中引用类方法会导致自动加载循环依赖崩溃 - 2

    代码:threads=[]Thread.abort_on_exception=truebegin#throwexceptionsinthreadssowecanseethemthreadseputs"EXCEPTION:#{e.inspect}"puts"MESSAGE:#{e.message}"end崩溃:.rvm/gems/ruby-2.1.3@req/gems/activesupport-4.1.5/lib/active_support/dependencies.rb:478:inload_missing_constant':自动加载常量MyClass时检测到循环依赖稍加研究后,

随机推荐