You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 8.0添加AddApplicationPart与EmbeddedFileProvider后内存泄漏

排查ASP.NET Core 8.0模块迁移后xUnit测试内存泄漏问题

核心问题分析

你添加EmbeddedFileProvider到Razor运行时编译选项后,每次测试初始化新服务容器时,该文件提供者关联的程序集及Razor编译缓存可能未被正确释放。Razor运行时编译会维护编译结果的缓存,若每次测试都新建EmbeddedFileProvider实例并关联同一程序集,再加上测试上下文的服务容器未完全清理,就会导致编译相关实例持续堆积,最终引发内存暴涨。

排查与解决建议

  • 复用EmbeddedFileProvider实例
    不要在每次服务配置时新建EmbeddedFileProvider,改为静态单例避免重复注册:

    private static readonly EmbeddedFileProvider _embeddedFileProvider = 
        new EmbeddedFileProvider(typeof(HomeController).Assembly);
    
    // 测试服务配置中使用
    services.AddControllersWithViews()
            .AddApplicationPart(typeof(HomeController).Assembly)
            .AddRazorRuntimeCompilation(options =>
                {
                    // 避免重复添加同一文件提供者
                    if (!options.FileProviders.Any(fp => fp == _embeddedFileProvider))
                    {
                        options.FileProviders.Add(_embeddedFileProvider);
                    }
                });
    

    重复添加会导致Razor编译系统多次处理同一程序集的视图资源,生成多份编译缓存,额外占用内存。

  • 禁用测试环境的Razor编译缓存
    测试场景下无需保留编译缓存,可通过代码或配置关闭:

    .AddRazorRuntimeCompilation(options =>
    {
        options.FileProviders.Add(_embeddedFileProvider);
        // 设置缓存大小为0,禁用编译结果缓存
        options.CacheSizeLimit = 0;
    })
    

    也可在appsettings.Test.json中添加配置:

    {
      "Razor": {
        "RuntimeCompilation": {
          "CacheSizeLimit": 0
        }
      }
    }
    

    这样编译相关实例不会被缓存持有,更易被GC回收。

  • 确保测试服务主机完全释放
    检查测试基类是否正确实现IDisposable,在测试结束时主动停止并释放服务主机:

    public class TestBase : IDisposable
    {
        private IHost _host;
    
        protected IHost Host => _host ??= CreateHost();
    
        private IHost CreateHost()
        {
            return Host.CreateDefaultBuilder()
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .Build();
        }
    
        public void Dispose()
        {
            _host?.StopAsync().Wait();
            _host?.Dispose();
            _host = null;
        }
    }
    

    必须调用StopAsync()和Dispose(),确保Razor编译相关的后台服务被正确停止释放。

  • 检查嵌入式资源配置
    确认模块项目中视图文件的「生成操作」设为「嵌入式资源」,且无重复嵌入同一视图的情况。重复的嵌入式资源会让Razor编译系统处理冗余资源项,增加内存占用。

  • 定位缓存持有源
    使用Visual Studio内存探查器对比测试前后的内存快照,筛选RazorCompilation相关类型(如RazorCompiledItem、CompilerCache),查看引用链,找到持有这些实例不释放的对象——通常是测试服务容器中的单例服务或静态缓存。

  • 尝试共享服务容器(谨慎使用)
    若所有测试依赖相同模块程序集,可通过xUnit的ICollectionFixture在集合级别初始化一次服务容器,避免每个测试重复创建:

    public class SharedServiceFixture : IDisposable
    {
        public IHost Host { get; }
    
        public SharedServiceFixture()
        {
            Host = Host.CreateDefaultBuilder()
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                })
                .Build();
            Host.StartAsync().Wait();
        }
    
        public void Dispose()
        {
            Host.StopAsync().Wait();
            Host.Dispose();
        }
    }
    
    [CollectionDefinition("SharedServiceCollection")]
    public class SharedServiceCollection : ICollectionFixture<SharedServiceFixture>
    {
    }
    
    [Collection("SharedServiceCollection")]
    public class MyTests
    {
        private readonly SharedServiceFixture _fixture;
    
        public MyTests(SharedServiceFixture fixture)
        {
            _fixture = fixture;
        }
    }
    

    注意:此方式会共享服务状态,可能导致测试间污染,仅适合无状态API测试。

内容的提问来源于stack exchange,提问作者Erik Steinebach

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 09:50:03