【问题标题】:yield return - memory optimizationyield return - 内存优化
【发布时间】:2015-03-20 18:00:46
【问题描述】:

还有一个关于yield return的问题

所以我需要远程执行不同的 SQL 脚本。脚本在 TFS 中,所以我会自动从 TFS 获取它们,并且该过程会遍历所有文件,读取它们在内存中的内容并将内容发送到远程 SQL 服务器。

到目前为止,该过程完美无缺。但现在一些脚本将包含bulk inserts,将脚本的大小增加到 500,000 MB 或更多。

所以我构建了代码“thinking”,我曾经在内存中读取文件的内容,但现在我有了第二个想法。

这就是我所拥有的(过于简化):

    public IEnumerable<SqlScriptSummary> Find(string scriptsPath)
    {
        if (!Directory.Exists(scriptsPath))
        {
            throw new DirectoryNotFoundException(scriptsPath);
        }

        var path = new DirectoryInfo(scriptsPath);

        return path.EnumerateFiles("*.sql", SearchOption.TopDirectoryOnly)
            .Select(x =>
            {
                var script = new SqlScriptSummary
                {
                    Name = x.Name,
                    FullName = x.FullName,
                    Content = File.ReadAllText(x.FullName, Encoding.Default)
                };

                return script;
            });
    }

....

    public void ExecuteScripts(string scriptsPath)
    {
        foreach (var script in Find(scriptsPath))
        {
            _scriptRunner.Run(script.Content);
        }
    }

我的理解是EnumerateFilesyield return 一次处理每个文件,所以这就是让我“认为”我在内存中一次加载一个文件的原因。

但是……

一旦我对它们进行迭代,在 ExecuteScripts 方法中,在 foreach 循环中使用的 script 变量退出后会发生什么情况范围?那是处置吗?还是留在记忆中?

  • 如果它保留在内存中,这意味着即使当我使用迭代器并在内部使用yield return 时,当我遍历所有它们时它们仍然在内存中,对吗?所以最后它就像使用 ToList 只是懒惰执行,对吗?

  • 如果script 变量在超出范围时被释放,那么我想我会没事的

如何重新设计代码以优化内存消耗,例如强制一次将脚本的内容加载到内存中

其他问题:

  • 如何测试(单元/集成测试)我在内存中一次只加载一个脚本?

  • 如何测试(单元/集成测试)每个脚本是否已从内存中释放/未从内存中释放?

【问题讨论】:

    标签: c# memory memory-management iterator yield-return


    【解决方案1】:

    一旦我对它们进行迭代,在 foreach 循环中使用的脚本变量超出范围后会发生什么情况?那是处置吗?还是留在记忆中?

    如果您的意思是在 ExecuteScripts 方法中 - 没有什么可处理的,除非 SqlScriptSummary 实现 IDisposable,这似乎不太可能。但是,这里有两件事:

    • script 变量在 foreach 循环后超出范围,不能充当 GC 根
    • script 变量所引用的每个对象都可以在没有其他对象引用时进行垃圾回收...包括下一次迭代中的 script

    所以是的,基本上应该没问题。您将一次加载一个文件,就 GC 无法收集的对象而言,我看不出内存中一次有多个文件内容的任何原因。 (GC 本身是惰性的,因此内存中不太可能恰好一次在内存中存在一个脚本,但您不必担心这方面的事情,因为您的代码可以确保它不会一次保留对多个脚本的实时引用。)

    您可以测试一次只加载一个脚本的方法是使用包含大型脚本(实际上并没有做任何事情)的大目录进行尝试。如果您可以处理的脚本多于您的内存,那很好:)

    【讨论】:

    • 所以@JonSkeet,如果我理解正确的话,script 变量将被 GC 收集,但不会完全在 foreach 循环中的每次迭代之后收集,对吧?所以这意味着我有时可以一次在内存中加载一两个文件,但 GC 会确保稍后收集它们,对吗?
    • @Jupaol:不收集变量。对象是。你需要区分这两者。但是,是的,除此之外,这听起来是对的。
    • @Jupaol GC 不收集变量,对象是。变量只是指向内存中某处存在的对象(对于引用类型,值类型有点不同)。如果 GC 确定没有变量指向某个对象,则该对象有资格被收集,这意味着在将来的某个时间,它的内存将被回收。编辑:被乔恩·斯基特(Jon Skeet)以确切的措辞@_@殴打。
    • @Kyle Jon Skeet 无法被击败。感谢您的解释,我需要阅读更多关于 GC 的内容
    猜你喜欢
    • 2019-05-12
    • 1970-01-01
    • 2011-09-08
    • 2017-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-18
    • 2011-03-10
    相关资源
    最近更新 更多