我正在调查应用程序中的内存泄漏。上下文如下:
让我们假设我必须处理不同类型的XML文件,并且我每天都会收到大量XML文件,所以我有一个IXmlProcessor
接口。
public interface IXmlProcessor
{
void ProcessXml(string xml);
}
以及一些具体的XML处理器。
public class UserXmlProcessor : IXmlProcessor
{
private readonly IUserRepository _userRepository;
public UserXmlProcessor(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public void ProcessXml(string xml)
{
// do something with the xml
// call _userRepository
}
}
所有IXmlProcessor
具体类型都注册到DI容器中,为了解决这些问题,我有一个Factory类,它也注册到了DI容器中
public class XmlProcessorFactory where TType : class
{
private readonly IServiceProvider _serviceProvider;
public XmlProcessorFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IXmlProcessor GetImplementation(string identifier)
{
var type = FindType(identifier);
return _serviceProvider.GetService(type) as IXmlProcessor;
}
private Type FindType(string identifier)
{
// do some reflection to find the type based on the identifier (UserXmlProcessor, for example)
// don't worry, there's caching to avoid unecessary reflection
}
}
在某个时刻,我称之为
public class WorkItem
{
public string Identifier { get; set; }
public string Xml { get; set; }
}
public class WorkingClass
{
private readonly XmlProcessorFactory _xmlProcessorFactory;
public WorkingClass(XmlProcessorFactory xmlProcessorFactory)
{
_xmlProcessorFactory = xmlProcessorFactory;
}
public void DoWork(WorkItem item)
{
var processor = _xmlProcessorFactory.GetImplementation(item.Identifier);
processor.ProcessXml(item.Xml);
}
}
IUserRepository
是一个简单的实现,具有实体框架上下文。
所以,问题来了:根据微软的文档:
从容器解析的服务永远不应该由开发人员处理。
通过DI接收IDisposable依赖项不需要接收器自己实现IDisposaable。IDisposable依赖项的接收器不应该对该依赖项调用Dispose。
因此,如果我将IUserRepository注入控制器,那没关系,容器将处理对象的处置以及EF上下文的处置,没有一个需要是IDisposable的。
但是我的Xml处理器呢?文件上写着:
不是由服务容器创建的服务
开发人员负责处理服务。
避免使用服务定位器模式。例如,当您可以使用DI时,不要调用GetService来获取服务实例。另一个需要避免的服务定位器变体是注入一个在运行时解析依赖关系的工厂。这两种做法都混合了控制反转策略。
而且_ = serviceProvider.GetRequiredService<ExampleDisposable>();
也是反模式。但正如您所看到的,我确实需要在运行时基于XML标识符来解决依赖关系,并且我不想求助于切换情况。
因此:
- IXmlProcessors是否应该实现IDisposable并手动发布IUserRepository
- 我是否也应该级联并使IUserRepository实现IDisposable以发布EntityContext
- 如果是这样的话,如果它被注入控制器,这不会影响使用寿命吗
而且
_ = serviceProvider.GetRequiredService<ExampleDisposable>();
也是反模式。
这句话太简单了。调用GetRequiredService
是,而不是当从组合根内的调用时服务定位器反模式的实现,因此是可以的。当在CompositionRoot外部调用时,它是ServiceLocator反模式的实现。打电话的大多数缺点GetRequiredService
只有在组合根外部使用时才存在。
IXmlProcessors是否应该实现IDisposable并手动发布IUserRepository?
否。Microsoft文档是正确的。当从容器解析IUserRepository时,容器将确保它(或其依赖项)得到处理。在IUserRepository的使用者中添加处置逻辑来处置存储库只会导致使用者不必要的复杂性。依赖项只会被处理两次。
我是否也应该级联并使IUserRepository实现IDisposable以释放EntityContext?
否。当EntityContext
由DI容器管理时,它将再次确保它被处理掉。因此,IUserRepository实现应该而不是实现处置,以确保EntityContext
得到处置。容器将执行此操作。
如果是这样,如果它被注入控制器,这不会影响使用寿命吗?
在消费者上实现IDisposable
的问题之一是,这会波及整个系统。使低级别的依赖关系是可丢弃的,将迫使您使依赖链中的所有消费者也是可丢弃的。这不仅会导致使用者(不必要的)复杂性,还会迫使系统中的许多类进行更新。这也意味着需要为所有这些类添加测试。这将是违反开放/封闭原则的典型例子。
请注意,使用默认的.NET Core DI容器,很容易意外导致内存泄漏。当您直接从根容器解析一次性Scoped或Transient组件,而不是从IServiceScope
解析它们时,就会发生这种情况。特别是一次性瞬态组件是令人讨厌的,因为一开始它似乎可以工作(因为你总是会得到一个新的实例),但这些一次性瞬态将一直保持活动,直到容器本身被处理掉,这通常只会在应用程序关闭时发生。
因此,请确保始终从服务范围进行解析,而不是从根容器进行解析(运行短暂的(控制台)应用程序时除外)。