.NET Core - 依赖注入、工厂和IDisposable

3

我正在调查应用程序中的内存泄漏。以下是上下文:

假设我需要处理不同类型的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 容器中,为了解决它们的依赖,我有一个工厂类,也已注册到 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是一个简单的实现,拥有Entity Framework上下文。

所以,这里有一个问题:根据微软的文档

从容器中解析的服务不应由开发人员处理。

通过 DI 接收 IDisposable 依赖关系不需要接收方本身实现 IDisposable。IDisposable 依赖关系的接收者不应调用该依赖关系的 Dispose。

因此,如果我将 IUserRepository 注入到控制器中,那么可以,容器将处理对象的处理以及 EF Context 的处理,都不需要实现 IDisposable。

但是,我的 Xml Processors 怎么办?文档说:

未由服务容器创建的服务

开发人员负责释放服务。

避免使用服务定位器模式。例如,当您可以使用 DI 时,请勿调用 GetService 来获取服务实例。 另一个要避免的服务定位器变体是注入在运行时解析依赖项的工厂。这两种做法混合了控制反转策略。

并且 _ = serviceProvider.GetRequiredService<ExampleDisposable>(); 是一种反模式。但是,正如您所看到的,我确实需要根据 XML 标识符在运行时解析依赖项,而且我不想采用 switch cases 的方式。

所以:

  • 是否应让 IXmlProcessors 实现 IDisposable 并手动释放 IUserRepository?
  • 是否应级联并使 IUserRepository 实现 IDisposable 以释放 EntityContext?
  • 如果是这样,那会不会影响服务的生命周期,如果将其注入到控制器中?
1个回答

4
这个语句过于简单化。当在组合根内部调用时,调用GetRequiredService并不是服务定位器反模式的实现,因此是可以的。当在组合根之外调用时,它就成为了服务定位器反模式的实现。大多数调用GetRequiredService所带来的负面影响只存在于组合根之外使用时。

IXmlProcessors应该实现IDisposable并手动释放IUserRepository吗?

不需要。Microsoft文档是正确的。当您从容器中解析出IUserRepository时,容器将确保它(或其依赖项)被处理。在IUserRepository的使用者中添加处理逻辑以仅释放存储库只会导致消费者中不必要的复杂性。依赖项只会被处理两次。

不需要。当EntityContext由DI容器管理时,它将确保它被处理。因此,IUserRepository实现不应实现处理,以确保EntityContext被处理。容器会做到这一点。

在使用者上实现IDisposable的一个问题是,这会影响整个系统。使低级依赖项可处理将迫使您使所有依赖项链中的消费者都可处理。这不仅会导致消费者中的(不必要的)复杂性,还会强制更新系统中的许多类。这也意味着必须为所有这些类添加测试。这将是开闭原则违规的典型例子。

请注意,使用默认的.NET Core DI容器很容易意外导致内存泄漏。当您从根容器直接解析一次性范围或瞬态组件时,就会发生这种情况,而不是从IServiceScope中解析它们。特别是一次性的瞬态组件非常令人讨厌,因为起初似乎可以工作(因为您总是得到一个新实例),但这些一次性瞬态组件将被保持活动状态,直到容器本身被处理掉,这通常只会在应用程序关闭时发生。
因此,确保您始终从服务范围解析,而不是从根容器解析(除非您运行短暂的(控制台)应用程序)。

1
谢谢!我觉得现在我找到了我的问题。该应用程序是一个Windows服务。我刚刚意识到,我只创建了一个服务范围,当应用程序启动时。这意味着短暂资源仅在单个范围被处置,即应用程序关闭时才会被处置,对吗? - Matheus Lemos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接