尽可能的将你的服务注册成Transient服务。设计一个Transient服务是相对简单的,因为你通常不需要关心多线程和内存泄漏的问题,而且这些服务生命周期相对短。
小心使用Scoped服务,因为当你创建子作用域或者在非web应用程序使用Scoped服务,会出现一些棘手的问题。
小心使用Singleton服务,因为你需要正确处理多线程问题和潜在的内存泄露问题。
不要在Singleton服务中依赖一个Transient服务或Scoped服务。因为这时Transient服务会变成Singleton服务,如果Transient服务不支持单例场景,当Singleton服务注入Transient服务时会产生异常问题。ASP.NET Core默认依赖注入容器在这种场景下会抛异常。
在方法内解析服务在某些场景下,你可能需要在服务的方法中解析另外一个服务。这种情况下请确保在使用服务后及时释放服务。这才是创建范围作用域服务的最佳方式。
public class PriceCalculator { private readonly IServiceProvider _serviceProvider; public PriceCalculator(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public float Calculate(Product product, int count, Type taxStrategyServiceType) { using (var scope = _serviceProvider.CreateScope()) { var taxStrategy = (ITaxStrategy)scope.ServiceProvider .GetRequiredService(taxStrategyServiceType); var price = product.Price * count; return price + taxStrategy.CalculateTax(price); } } }PriceCalculator在构造函数里注入了IServiceProvider并赋值给以只读字段。然后PriceCalculator在Calculate的方法内创建了一个子范围作用域。使用scope.ServiceProvider来解析服务依赖,而不是用_serviceProvider实例。这样,在子范围作用域内被解析的所有服务会在using的声明结束后自动释放。
良好实践
如果在方法内解析服务,请始终创建子范围作用域,以确保已解析的服务被正确释放。
如果一个方法使用IServiceProvider作为参数,那么可以直接使用它解析服务依赖,而不需要关心依赖服务是否释放。创建/管理服务范围作用域是调用方法代码的职责。遵循这一原则可以使代码更简洁。
不要保存对已解析服务的引用!否则,在使用对象引用时访问已释放的服务可能会导致内存泄漏(除非已解析的服务是单例的)。
单例服务 Singleton Services单例服务通常为了保持应用程序状态而设计。缓存是一个应用程序状态的最好示例。
public class FileService { private readonly ConcurrentDictionary<string, byte[]> _cache; public FileService() { _cache = new ConcurrentDictionary<string, byte[]>(); } public byte[] GetFileContent(string filePath) { return _cache.GetOrAdd(filePath, _ => { return File.ReadAllBytes(filePath); }); } }FileService只是简单的缓存了文件内容来减少磁盘读取。像这样的服务应该设计成单例服务。否则缓存将不能正常工作。
良好实践
如果一个服务持有某种状态,应该以线程安全的方式访问这个状态。因为所有的请求将并发的访问同一个实例,使用ConcurrentDictionary而不是Dictionary来确保线程安全。
不要在单例服务内使用Scoped/Transient服务,因为Transient服务可能不是线程安全的设计。如果确实需要使用,请注意多线程(例如使用Lock)。
引起内存泄漏的通常是由单例服务引起的。在应用程序结束之前,单例服务不会被释放。它们实例化类(或注入实例)也不会提前被释放,它们也会一直留在内存中,直到应用程序结束。确保在适当的时候释放服务,请参阅。
如果使用缓存数据(例如上述代码示例中文件内容的缓存),应该创建一种机制当原始数据发生变更的时候去更新或淘汰已缓存的数据(示例中当磁盘的文件变更时应该更新缓存)。
范围作用域服务Scoped Services范围作用域服务似乎是一个为每个web请求存储数据的候选方式。因为ASP.NET Core为每一个Web请求都会创建一个服务范围作用域。因此一个服务注册成Scoped服务,在Web请求过程可以共享这个服务。
public class RequestItemsService { private readonly Dictionary<string, object> _items; public RequestItemsService() { _items = new Dictionary<string, object>(); } public void Set(string name, object value) { _items[name] = value; } public object Get(string name) { return _items[name]; } }如果RequestItemsService注册成范围作用域的服务,并将RequestItemsService注入到两个不同的服务中,这两个服务可以访问到另外一个服务添加的数据,因为这两个服务在一个Web请求中是共享RequestItemsService实例的。