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 - 依赖注入(三)的更多相关文章

  1. # ASP.NET Core依赖注入解读&使用Autofac替代实现

    标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...

  2. [译]ASP.NET Core依赖注入深入讨论

    原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...

  3. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  4. ASP.NET Core依赖注入解读&使用Autofac替代实现【转载】

    ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Autofac实现和自定义实现扩展方法 3.1 安装Autof ...

  5. 实现BUG自动检测 - ASP.NET Core依赖注入

    我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...

  6. asp.net core 依赖注入几种常见情况

    先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...

  7. 自动化CodeReview - ASP.NET Core依赖注入

    自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...

  8. ASP.NET Core 依赖注入最佳实践——提示与技巧

    在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...

  9. ASP.NET Core依赖注入最佳实践,提示&技巧

    分享翻译一篇Abp框架作者(Halil İbrahim Kalkan)关于ASP.NET Core依赖注入的博文. 在本文中,我将分享我在ASP.NET Core应用程序中使用依赖注入的经验和建议. ...

  10. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

随机推荐

  1. Java单例模式的最佳实践?

    "读过书,--我便考你一考.茴香豆的茴字,怎样写的?"--鲁迅<孔乙己> 0x00 大纲 目录 0x00 大纲 0x01 前言 0x02 单例的正确性 new关键字 c ...

  2. BeanShell 后置处理器/前置处理器实现urldecode 解码

    1.使用正则/Json提取器提取需要解码的值 2.在提取的接口中添加后置处理器或在下个调用接口中添加前置处理器 3.编码实现 String token = vars.get("access_ ...

  3. JavaScript:操作符:比较运算符及其隐式转换数据类型

    不等关系 即大于>:大于等于>=:小于<:小于等于<= 当比较的两个变量,有非数字时,会隐式转换为数字再比较,转换情况同算术运算符: 当两个变量均为字符串时,不会进行转换,而是 ...

  4. windows通过sshfs挂载linux目录

    之前讲过一种方法,PC跟VM在同局域网的情况下,可以用samba的方式挂载linux系统的目录到windows上.但是当PC跟VM不同局域网时这种方式就没办法了. 网络环境 在示意图中,PC只能直连物 ...

  5. 使用nodejs编写api接口并部署到服务器上

    一.用node.js编写api接口 1.安装node环境,没有就去下载nodejs, 下载地址 2.创建一个node项目, 新建一个目录文件,例node_proxy 3.在新建的node项目执行npm ...

  6. 高并发解决方案orleans实践

    开具一张图,展开来聊天.有从单个服务.consul集群和orleans来展开高并发测试一个小小数据库并发实例. 首先介绍下场景,创建一个order,同时去product表里面减掉一个库存.很简单的业务 ...

  7. Linux c 程序自动启动自己

    在程序自动升级的时候需要自己重新启动自己 #include <stdio.h> #include <stdlib.h> #include <unistd.h> in ...

  8. 【力扣】剑指 Offer II 092. 翻转字符

    题目 解题思路 一个很暴力的想法,在满足单调递增的前提下,使每一位分别取 1 或 0,去看看哪个结果小. 递归函数定义int dp(StringBuilder sb, int ind, int pre ...

  9. 在Mac OS上将Node.js连接到XAMPP MySQL服务器一直报错error connecting: Error: connect ECONNREFUSED

    以下為通過node.js連線本機mysql資料庫的方法: var mysql = require('mysql'); var connection = mysql.createConnection({ ...

  10. yarn使用 以及和npm对比

    yarn是facebook发布的一款取代npm的包管理工具. yarn的特点: 速度超快. Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载. 同时利用并行下载以最大化资源利用率,因此安装速 ...