【问题标题】:Improving memory management in a Net Core Windows-Service改进 Net Core Windows 服务中的内存管理
【发布时间】:2020-04-17 15:40:01
【问题描述】:

我想询问拥有控制台应用程序的最佳方法,该应用程序也可以在网络核心环境中用作 Windows 服务。问题不在于拥有这样的应用程序,而在于执行的代码。 我试图解释我的问题到底是什么。

如果windows-service启动了,就会启动一个for循环,它会做几件事

  • 访问亚马逊 AWS SQS
  • 通过 HTTP 请求访问 csv 文件 => 正在使用这些数据并部分存储在 db 中
  • 通过 EF 访问 oracle db 的表(插入、更新和删除)

到目前为止一切顺利。一切都按我的意愿进行。使用依赖注入(Scoped)在我的循环中访问我为完成所有操作而编写的那些方法。

棘手的部分是这个应用程序的内存使用量相当......不是最佳的。它做了它的工作,但是在下载和使用 csv 文件的数据时,应用程序使用了太多的内存并且没有正确释放。您对如何处理此类场景(windows-service 中的循环)有任何建议或知识库文章吗?

我试图释放我所能做的一切,比如清除列表并将它们设置为 null,在查询数据时禁用 EF 中的任何跟踪(也禁用额外的插入/更新 changetracker)和使用“使用语句”(/处置元素)。

另外,我想补充一点,我正在使用最新的 Amazon AWS 开发工具包(Core 和 SQS)和 EF Core 2.2.6 以及 Oracle Provider。

你有什么提示吗?

如果您需要更多信息,请告诉我。然后我会根据需要提供更多数据。

亲切的问候


关于读取csv文件的注释

从 URL 读取文件。

        using (var response = await request.GetResponseAsync())
            {
                await using (var receiveStream = response.GetResponseStream())
                {
                    using (var readStream = new StreamReader(receiveStream, Encoding.UTF8))
                    {
                        var content = readStream.ReadToEnd();
                        result.Content = content.Split('\n').ToList();

                        result.IsSuccess = true;
                    }
                }
            }

阅读后,我将其转换为我的目标类

public static async Task<List<Curve>> ReturnCurveData(List<string> content)
    {
        var checkVar = -1;
        var list = new List<Curve>();

        foreach (var entry in content)
        {
            if (string.IsNullOrEmpty(entry)) continue;

            var entrySplitted = entry.Split('|');
            if (entrySplitted.Length < 3) continue;
            else if(!int.TryParse(entrySplitted[0], out checkVar)) continue;

            var item = new Curve();
            item.Property1 = Convert.ToInt32(entrySplitted[0]);
            item.Property2 = (entrySplitted.Length ==3) ? DateTime.Now : Convert.ToDateTime(entrySplitted[1]);
            item.Property3 = (entrySplitted.Length ==3) ? Convert.ToDateTime(entrySplitted[1]) : Convert.ToDateTime(entrySplitted[2]);
            item.Value = (entrySplitted.Length ==3) ? Convert.ToDecimal(entrySplitted[2], new CultureInfo("en-US")): Convert.ToDecimal(entrySplitted[3], new CultureInfo("en-US"));

            list.Add(item);
        }

        return await Task.FromResult(list);
    }

关于范围的定义

var hostBuilder = new HostBuilder()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .ConfigureAppConfiguration((hostingContext, config) =>
            {
                ...
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddScoped<Data.Queries.Database.Db>();
                services.AddScoped<Data.Queries.External.Aws>();
                services.AddScoped<Data.Queries.External.Web>();
                services.RegisterEfCoreOracle<DbContext>(AppDomain.CurrentDomain.BaseDirectory,
                    "cfg_db.json");
                services.AddScoped<IExecute, Execute>();
                services.AddHostedService<ExecuteHost>();
            })
            .ConfigureLogging((hostingContext, logging) =>
            {
                ...
            });

public static void RegisterEfCoreOracle<T>(this IServiceCollection services, string configurationDirectory, string configurationFile, ServiceLifetime lifetime = ServiceLifetime.Scoped) where T : DbContext
    {
        //Adding configuration file
        IConfiguration configuration = new ConfigurationBuilder()
            .SetBasePath(configurationDirectory)
            .AddJsonFile(configurationFile, optional: false)
            .Build();

        services.Configure<OracleConfiguration<T>>(configuration);

        var oraConfig = services.ReturnServiceProvider().GetService<IOptions<OracleConfiguration<T>>>();

        if (oraConfig.Value.Compatibility != null)
        {
            services.AddDbContext<T>(o => o
                .UseOracle(oraConfig.Value.ConnectionString(), b => b
                    .UseOracleSQLCompatibility(oraConfig.Value.Compatibility)), lifetime);
        }

        else
        {
            services.AddDbContext<T>(o => o
                .UseOracle(oraConfig.Value.ConnectionString()), lifetime);
        }
    }

【问题讨论】:

  • 听起来您正在将整个 .csv 文件加载到内存中,这是您永远不应该这样做的。但是,如果没有代码或分析支持,很难说。
  • 同意此评论。如果可能,您应该使用 Stream。但是,如果你不能这样做,你可以释放对象并强制 GC。不明白为什么你不能流式传输
  • 我更新了我的初始帖子。如果从您的角度来看不是最好的方法,也许您可​​以告诉您如何以不同的方式进行操作? @盲人
  • 如何定义范围?您不想将单个 EF DbContext 保留很长时间。 ASP.NET 通过为每个请求定义范围来为您做到这一点。但在长期运行的服务中,您要么必须创建自己的范围,要么手动管理 DbContext 的生命周期。
  • @DavidBrowne-Microsoft 我编辑了我的原始帖子。我希望这能澄清我所说的“范围”的含义。

标签: c# performance entity-framework .net-core


【解决方案1】:

既然您发布了代码,我们可以很容易地分析问题:

var content = readStream.ReadToEnd();

正如我所说,永远不要读取内存中的文件,逐行处理它,例如使用 StreamReaderStringReader,或 Nuget 中的任何一百万个 csv 解析器。

 result.Content = content.Split('\n').ToList();

您不仅要读取内存中的整个文件,还要将其拆分为值(因此除了整个文件内容,您还分配了一个长度等于行数,并为每一行分配由逗号分隔的每个元素的字符串,总共行*元素字符串),并且除此之外分配另一个列表并复制内容将数组放入其中。

编辑:您在此处将其拆分为行,并在第二部分将其拆分为值。我的分析是正确的,但是问题被分成了多行。

不用说这充其量是可笑的。停止使用Split,不要不必要地使用ToList,也不要一次阅读所有内容。您正在编写一个 Web 服务器,理论上所有这些都可以为每个处理的请求完成一次,根据您的 CPU,这可以很容易地进入几十个。

我不会讨论第二部分,但它更糟。一目了然,我看到分配了更多列表,甚至更多Splits。而return await Task.FromResult(list); 这一行表明你根本不了解异步函数。不仅你所拥有的根本不是异步的,而且如果你为了好玩而坚持让它异步,请直接返回列表,而不是作为等待的任务。

【讨论】:

  • 感谢您的广泛评论。我会在这方面工作。我错过了关于我正在实施我的应用程序的内容和方式的某种反思。因此,欢迎每一个反馈,这将引导我进一步改进和理解使用 c#。
  • 我们都得从某个地方开始!
  • 再来一个快速评论,千万不要在没有非常好的理由的情况下使用decimal。我可以从与波​​音公司合作的直接经验告诉你,float 不足以准确地表示这个星球上的一个点(即飞机),但double 是完全稳定的。我想不出比商用飞机的自动驾驶软件更精确的东西了。
  • 谢谢。我会记住的。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-15
  • 2020-05-02
  • 2021-11-17
  • 2019-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多