簡體   English   中英

.NET 框架的 Microsoft.Extensions.DependencyInjection 導致 memory 泄漏

[英]Microsoft.Extensions.DependencyInjection for .NET Framework causing memory leak

由於不推薦使用 Unity,我最近將 .Net Framework 4.7.2 MVC 項目從 Unity 移到了 Microsoft.Extensions.DependencyInjection。 切換看起來很簡單,主要的變化是需要創建一個自定義的DependencyResolver ,因為這之前是由 Unity 處理的。

現在這些更改已投入生產,我開始注意到一些嚴重的 memory 問題。 獲取 memory 用法的轉儲顯示 memory 中最大的項目是來自 Microsoft.Extensions.DependencyInjection 的ServiceProvider ,其中包含數千個尚未處理的控制器。

DependencyResolver 看起來像:

public class MicrosoftDefaultDependencyResolver
    : System.Web.Mvc.IDependencyResolver
    , System.Web.Http.Dependencies.IDependencyResolver
{
    protected IServiceProvider serviceProvider;

    public MicrosoftDefaultDependencyResolver(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public IDependencyScope BeginScope()
    {
        return new MicrosoftDefaultDependencyResolver(
            this.serviceProvider.CreateScope().ServiceProvider);
    }

    public void Dispose()
    {
    }

    public object GetService(Type serviceType)
    {
        return this.serviceProvider.GetService(serviceType);
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return this.serviceProvider.GetServices(serviceType);
    }
}

我已經根據我閱讀的 stackoverflow 文章實現了這一點: How do I inject dependency in webapi in .net framework using Microsoft.Extensions.DependencyInjection?

Startup class 看起來像:

public void Configuration(IAppBuilder app)
{
    // Set MVC Resolver
    MicrosoftDefaultDependencyResolver resolver = GetDependencyResolver();

    DependencyResolver.SetResolver(resolver);

    // Any connection or hub wire up and configuration should go here
    app.MapAzureSignalR(GetType().FullName);

    // Turn tracing on programmatically
    GlobalHost.TraceManager.Switch.Level = SourceLevels.Error;
}

由於我們仍在使用 .Net 框架,控制器似乎沒有自動注冊,因此我不得不將它們顯式注冊為 Transient。

我的問題是,我是否完全遺漏了什么? 我希望轉向 Microsoft DI package 是它會像在較新版本的 .Net 中一樣 function,但我現在感覺更容易轉向完全不同的 IoC 框架,例如Autofaq,解決這些 memory 問題。

您的MicrosoftDefaultDependencyResolver存在缺陷。

如果您查看System.Web.Mvc.IDependencyResolver的定義,您會注意到沒有BeginScope方法。 MVC 不會自動啟動 scope。 這意味着您的所有 MVC 控制器都是從根范圍/容器解析的。 從根容器解析的任何作用域或臨時一次性依賴項都將被永久引用。

您可能已經注意到,只有您的 MVC 控制器被引用; Web API控制器沒有這個問題。 This is because the System.Web.Http.Dependencies.IDependencyResolver definition actually contains a BeginScope method and ASP.NET Web API actively calls this method at the begin of each request. 這意味着 Web API 控制器是從特定於請求的IServiceScope解析的,並且此 scope 將被自動垃圾收集。

盡管如此,在您的 Web API 實現中存在一個錯誤,因為您沒有處理IServiceScope方法。 盡管 Web API 控制器會自動處理掉,但來自容器的其他服務不會。

注意:我想說 MVC 中的 DI 實現有點缺陷,但這可能是 Web API 團隊首先創建新的IDependencyResolver接口的原因。

因此,MVC 的解決方案是確保(以某種方式)在每個請求上創建IServiceScope ,MVC 控制器從 scope 中得到解析,並在請求結束時處理 scope。

有幾種方法可以做到這一點,但可能最簡單的方法是掛鈎Application事件。 例如:

public class WebApiApplication : System.Web.HttpApplication
{
    private static IServiceProvider Container;

    // Store scope in HttpContext.Items
    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        HttpContext.Current.Items[typeof(IServiceScope)] =
            Container.CreateScope();
    }

    // Dispose the scope
    protected void Application_EndRequest(object sender, EventArgs e)
    {
        var scope =
            HttpContext.Current.Items[typeof(IServiceScope)] as IServiceScope;

        scope?.Dispose();
    }

    protected void Application_Start()
    {
        // All your usual MVC stuff here
   
        Container = BuildServiceProvider();

        System.Web.Mvc.DependencyResolver.SetResolver(
            new MsDiMvcDependencyResolver(Container));
    }
}

在此之后,最好創建一個單獨的 class 作為System.Web.Mvc.IDependencyResolver的適配器。 此實現可以使用存儲的IServiceScope

public class MsDiMvcDependencyResolver : System.Web.Mvc.IDependencyResolver
{
    private readonly IServiceProvider root;

    public MsDiMvcDependencyResolver(IServiceProvider root) => this.root = root;

    // Pulls the scope from the HttpContext and falls back to the root container.
    private IServiceProvider Current
    {
        get
        {
            var context = HttpContext.Current;

            if (context is null) return this.root;

            var scope = context.Items[typeof(IServiceScope)] as IServiceScope;

            if (scope is null) return this.root;

            return scope.ServiceProvider;
        }
    }

    public object GetService(Type serviceType) =>
        this.Current.GetService(serviceType);

    public IEnumerable<object> GetServices(Type serviceType) =>
        this.Current.GetServices(serviceType);
}

為了完整起見,這就是您的 Web API 依賴解析器應該看起來像:

public class MsDiWebAPiDependencyResolver
    : System.Web.Http.Dependencies.IDependencyResolver
{
    private readonly IServiceProvider root;

    public MsDiWebAPiDependencyResolver(IServiceProvider root) => this.root = root;

    public IDependencyScope BeginScope() =>
            new DependencyScope(this.root.CreateScope());

    public void Dispose() => (this.root as IDisposable)?.Dispose();

    public object GetService(Type serviceType) => this.root.GetService(serviceType);

    public IEnumerable<object> GetServices(Type serviceType) =>
            this.root.GetServices(serviceType);

    private sealed class DependencyScope : IDependencyScope
    {
        private readonly IServiceScope scope;

        public DependencyScope(IServiceScope scope) => this.scope = scope;

        public void Dispose() => this.scope.Dispose();

        public object GetService(Type serviceType) =>
            this.scope.ServiceProvider.GetService(serviceType);

        public IEnumerable<object> GetServices(Type serviceType) =>
            this.scope.ServiceProvider.GetServices(serviceType);
    }
}

最后一點,兩個依賴解析器實現仍然導致 controller 類型被處理兩次,這是因為 MVC 和 Web API 也處理控制器。 但是,這在正常情況下應該不會引起任何問題。

我本可以嘗試 Steven 的回答,但是當我們使用 Unity 時,它涉及到一些輸入很少的東西。 為簡單起見,我在最少的工作中切換到使用 AutoFac,memory 問題就消失了。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM