Microsoft.Extensions.DependencyInjection中的Transient依賴注入關系,使用不當會造成內存泄漏


Microsoft.Extensions.DependencyInjection中(下面簡稱DI)的Transient依賴注入關系,表示每次DI獲取一個全新的注入對象。但是使用Transient依賴注入關系時,最好要配合IServiceScope來一起使用,因為通過Transient依賴注入關系創建的對象,都會被創建它的ServiceProvider對象內部引用,這樣會造成注入對象無法被GC及時回收,造成內存泄漏,只有當調用ServiceProvider對象的Dispose方法后,ServiceProvider才會解除其內部對注入對象的引用,之后這些注入對象才能被GC回收。

我們新建一個.NET Core控制台項目,然后假設我們有接口IPeople和實現類People,他們之間的依賴注入關系是Transient。

現在,如果我們的代碼中有一個for循環,它會循環1000次,每一次都會從DI中獲取一個IPeople對象實例,由於接口IPeople和類People是Transient關系,所以每次DI都會創建一個新的People對象實例。但是我們只需要在每次循環中,調用People類的DoSomething方法做一些事情后,就不需要創建的People對象了,也就是說我們希望每次循環結束后,GC都能盡量回收在循環中創建的People對象實例。

所以我們寫了下面的代碼:

using Microsoft.Extensions.DependencyInjection;
using System;

namespace NetCoreDITransientInScope
{
    interface IPeople
    {
        void DoSomething();
    }

    class People : IPeople
    {
        public void DoSomething()
        {
            Console.WriteLine("DoSomething is running");
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();
            services.AddTransient<IPeople, People>();//注冊接口IPeople和類People的關系為Transient

            using (ServiceProvider rootServiceProvider = services.BuildServiceProvider())
            {
                //執行1000次循環,每一次循環創建一個People對象實例,在rootServiceProvider調用Dispose方法前,創建的1000個People對象實例都不會被GC回收
                for (int i = 0; i < 1000; i++)
                {
                    IPeople people = rootServiceProvider.GetService<IPeople>();
                    people.DoSomething();

                    //在每次循環結束后,創建的People對象實例無法被GC回收,因為在rootServiceProvider的內部對所有創建的Transient對象都保持了引用,除非調用rootServiceProvider的Dispose方法,否則在每次循環中創建的People對象實例都無法被GC回收
                }
            }

            Console.WriteLine("Press any key to end...");
            Console.ReadKey();
        }
    }
}

從上面代碼的注釋中,我們可以看到,實際上每一次for循環執行完后,GC並不能立即回收在循環中創建的People對象實例,原因是ServiceProvider對象rootServiceProvider的內部引用了由DI創建的所有People對象實例,除非調用rootServiceProvider的Dispose方法(也就是在上面using代碼塊最后),否則所有的People對象實例都無法被GC回收。設想一下,如果將上面的for循環改為一個死循環(對於有些后台服務程序而言,的確需要死循環),那么DI會創建大量的People對象實例無法被GC及時回收,造成內存泄漏。

所以正確使用Transient依賴注入關系的方法應該是,配合IServiceScope對象來使用,我們將上面的代碼改為如下:

using Microsoft.Extensions.DependencyInjection;
using System;

namespace NetCoreDITransientInScope
{
    interface IPeople
    {
        void DoSomething();
    }

    class People : IPeople
    {
        public void DoSomething()
        {
            Console.WriteLine("DoSomething is running");
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();
            services.AddTransient<IPeople, People>();//注冊接口IPeople和類People的關系為Transient

            using (ServiceProvider rootServiceProvider = services.BuildServiceProvider())
            {
                //執行1000次循環,每一次循環創建一個People對象實例
                for (int i = 0; i < 1000; i++)
                {
                    //在每一次循環中創建一個IServiceScope對象serviceScope,然后使用serviceScope的ServiceProvider創建People對象實例,這樣在rootServiceProvider中並沒有對People對象實例的引用,只有每次循環中創建的serviceScope中的ServiceProvider保持了People對象實例的引用,這樣在每次循環中當調用serviceScope的Dispose方法后,單次循環中創建的People對象實例就可以被GC回收了,而不是等到rootServiceProvider調用Dispose方法后,才能被GC回收
                    using (IServiceScope serviceScope = rootServiceProvider.CreateScope())
                    {
                        IPeople people = serviceScope.ServiceProvider.GetService<IPeople>();
                        people.DoSomething();
                    }
                }
            }

            Console.WriteLine("Press any key to end...");
            Console.ReadKey();
        }
    }
}

從上面代碼中,我們可以看到,由於現在在每次for循環中,是由一個獨立的IServiceScope對象serviceScope的ServiceProvider,來創建People對象實例,所以在for循環外面的ServiceProvider對象rootServiceProvider,其並沒有內部引用由DI創建的People對象實例。而在每次for循環中,我們都調用了serviceScope的Dispose方法(也就是在上面第二個using代碼塊最后),這樣每次循環結束后,就沒有任何代碼引用循環內的People對象實例了,GC就可以及時回收由DI創建的People對象實例。

 

在使用Microsoft.Extensions.DependencyInjection的Transient依賴注入關系時,一定要注意本文所述的內存泄漏問題,這個問題可能很多才開始接觸Microsoft.Extensions.DependencyInjection的開發人員不會注意到,但是它會嚴重影響你的程序性能和穩定性。可以參考下面兩篇帖子中發帖人提出的問題:

IServiceProvider garbage collection / disposal

When are .NET Core dependency injected instances disposed?

也可以參考在GitHub上,微軟官方對這個問題的討論:

Revisit tracking transient services for disposal

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM