.NET 9 中的服务生命周期:瞬态、作用域和单一实例

科技   2025-01-02 08:06   上海  


在ASP.NET Core中,依赖注入(DI)是一项关键特性,它能促进组件之间的松散耦合,使应用程序更具灵活性且更易于测试。依赖注入最重要的方面之一就是理解服务生命周期是如何工作的。掌握这方面的知识有助于你控制服务的生命周期,并优化应用程序中的资源使用情况。

本篇博客将涵盖以下内容:

  • 什么是服务生命周期?

  • ASP.NET Core中三种类型的服务生命周期:瞬态(Transient)、作用域(Scoped)和单例(Singleton)。

  • 每种生命周期的详细用例及代码示例。

  • 在决定使用哪种生命周期时的最佳实践。

到读完本篇博客时,你将对如何在ASP.NET Core.NET 9中使用服务生命周期来编写高效、可扩展的应用程序有扎实的理解。

什么是服务生命周期?

在ASP.NET Core中,当你在依赖注入(DI)容器中注册一项服务时,需要指定它的生命周期。这定义了一个服务实例的存活时长,以及它如何在不同组件和请求之间共享。

三种服务生命周期

  • 瞬态(Transient):每次请求该服务时都会创建一个新的实例。

  • 作用域(Scoped):会创建一个单一实例,并在同一个请求内共享。

  • 单例(Singleton):只创建一个实例,且在整个应用程序中共享。

为什么服务生命周期很重要?

选择正确的服务生命周期可确保高效利用资源、优化性能,并在应用程序中保持恰当的关注点分离。若生命周期使用不当,可能会导致内存泄漏、出现意外行为或效率低下等问题。

1. 瞬态服务生命周期

什么是瞬态服务?

每次从依赖注入容器中请求瞬态服务时,都会创建一个新的实例。这意味着每次将其注入到控制器或服务中时,都会创建一个新的实例。

特点:

  • 每次请求服务时都会创建一个新实例。

  • 无状态且轻量级的服务非常适合使用瞬态生命周期。

  • 由于服务在使用后会被释放,所以不会产生内存开销。

何时使用瞬态服务的示例:

当操作是无状态的,或者每个请求都需要服务的一个新实例时,可使用瞬态服务。例如,对于格式化器、映射器或简单计算等实用工具服务来说,瞬态服务是理想的选择。

瞬态服务代码示例:

public interface ITransientService
{
string GetOperationId();
}

public class TransientService : ITransientService
{
private readonly Guid _operationId;

public TransientService()
{
_operationId = Guid.NewGuid();
}

public string GetOperationId()
{
return $"瞬态服务操作ID:{_operationId}";
}
}

在这个示例中,每次请求该服务时,都会生成一个新的Guid。每个使用者都会获得一个不同的Guid,这体现了瞬态服务的无状态特性。

注册和使用瞬态服务:

Program.cs中:

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ITransientService, TransientService>();
}

在控制器中:

public class HomeController : Controller
{
private readonly ITransientService _transientService;

public HomeController(ITransientService transientService)
{
_transientService = transientService;
}

public IActionResult Index()
{
var id = _transientService.GetOperationId();
return Content(id);
}
}

关键点:每次访问该控制器时,都会创建一个TransientService的新实例,从而提供一个唯一的Guid值。

2. 作用域服务生命周期

什么是作用域服务?

作用域服务在每个HTTP请求中创建一次,并在参与该请求的组件之间共享。它在同一个请求内可重复使用,但不会在不同请求之间共享。

特点:

  • 每个请求会创建服务的一个单一实例,并在该请求内使用它的组件之间共享。

  • 对于那些在请求期间需要维护状态,但不应在不同请求之间共享的服务来说是理想的选择。

何时使用作用域服务的示例:

作用域服务非常适合数据库上下文类(如Entity Framework中的DbContext)。通常希望在整个请求中使用同一个上下文实例,以确保数据的一致性,并避免多次建立数据库连接。

作用域服务代码示例:

public interface IScopedService
{
string GetOperationId();
}

public class ScopedService : IScopedService
{
private readonly Guid _operationId;

public ScopedService()
{
_operationId = Guid.NewGuid();
}

public string GetOperationId()
{
return $"作用域服务操作ID:{_operationId}";
}
}

在这种情况下,每个请求会生成一个Guid,并在同一个请求内重复使用。

注册和使用作用域服务:

Program.cs中:

public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IScopedService, ScopedService>();
}

在控制器中:

public class HomeController : Controller
{
private readonly IScopedService _scopedService;

public HomeController(IScopedService scopedService)
{
_scopedService = scopedService;
}

public IActionResult Index()
{
var id = _scopedService.GetOperationId();
return Content(id);
}
}

关键点:对于每个HTTP请求,都会使用同一个ScopedService实例。然而,不同的请求将获得不同的实例。

3. 单例服务生命周期

什么是单例服务?

单例服务在整个应用程序的生命周期内只创建一次。该服务的所有使用者都将共享同一个实例,使其成为生命周期最长的类型。

特点:

  • 创建服务的一个单一实例,并在所有请求和使用者之间共享。

  • 对于需要在整个应用程序中持久化数据的有状态服务来说,单例服务是理想的选择。

何时使用单例服务的示例:

单例服务非常适合那些需要维护全局状态或执行不经常变更的操作的服务,例如日志记录、缓存或配置管理等。

单例服务代码示例:

public interface ISingletonService
{
string GetOperationId();
}

public class SingletonService : ISingletonService
{
private readonly Guid _operationId;

public SingletonService()
{
_operationId = Guid.NewGuid();
}

public string GetOperationId()
{
return $"单例服务操作ID:{_operationId}";
}
}

在这里,同一个Guid会在所有请求和使用者之间共享,因为它是在应用程序启动时生成一次的。

注册和使用单例服务:

Program.cs中:

public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISingletonService, SingletonService>();
}

在控制器中:

public class HomeController : Controller
{
private readonly ISingletonService _singletonService;

public HomeController(ISingletonService singletonService)
{
_singletonService = singletonService;
}

public IActionResult Index()
{
var id = _singletonService.GetOperationId();
return Content(id);
}
}

关键点:同一个SingletonService实例会在所有请求中重复使用,使其在应用程序的整个生命周期内持久存在。

选择服务生命周期的最佳实践

  • 对于每次都需要新实例的无状态、轻量级操作,使用瞬态服务。

  • 对于需要在请求内保持一致性,但不应在不同请求之间共享的操作,使用作用域服务。

  • 对于持有状态或实例化成本较高的全局共享服务,使用单例服务。

理解服务生命周期对于构建可扩展且高效的ASP.NET Core应用程序至关重要。通过为服务选择正确的生命周期,你可以确保资源的最优使用以及代码的可维护性。请记住:

  • 瞬态服务在每次请求时创建,非常适合无状态任务。

  • 作用域服务在单个请求的生命周期内存在,对于保持一致性很有帮助。

  • 单例服务在应用程序的整个生命周期内存在,非常适合全局状态或共享资源。

如果你喜欢我的文章,请给我一个赞!谢谢

架构师老卢
资深软件架构师, 分享编程、软件设计经验, 教授前沿技术, 分享技术资源(每天发布电子书),每天进步一点点...
 最新文章