【问题标题】:Right place to garbage collect File type in dispose pattern垃圾收集的正确位置 处置模式中的文件类型
【发布时间】:2020-09-16 04:23:10
【问题描述】:

现在我的同事之间就需要处理 C# 文件类型的位置进行了辩论。 (https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose)

查看下面的代码,当前 File.Delete 在 if(disposing) 部分中,它被认为是托管对象。有些人认为它需要在循环之外并且需要放置在非托管对象中。

~someService() => Dispose(false);

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
    GC.Collect();
}

bool disposed = false;
protected virtual void Dispose(bool disposing)
{
    if (disposed)
    {
        return;
    }

    if (disposing)
    {
        // Dispose managed state (managed objects).
        tempFilePaths?.ToList().ForEach(f =>
        {
            if (File.Exists(f))
            {
                File.Delete(f);
            }
        });

        this._engine?.Dispose();
    }

    // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
    // TODO: set large fields to null.
    this._fields = null;

    disposed = true;
}

【问题讨论】:

  • 归结为当对象没有被显式释放并且不再使用对象并且GC调用终结器是否应该删除文件时。如果在任何情况下都应该将其删除,则将其放在 if 块之外。然而,finalizer 应该避免做繁重的工作,因为 GC 是单线程的,并且一个缓慢的 finalizer 会减慢 GC 的工作。另一个问题是你永远不应该打电话给GC.Collect。让 GC 在有时间的时候处理清洗。
  • 但另一方面,我不想通过将托管对象放在 if(disposing) 之外来查看 ObjectDisposedException。如果需要快速释放内存,GC.Collect 似乎是必要的,所以我想知道为什么不应该调用它的强有力的推理。谢谢。
  • 为什么需要快速收集内存?当内存压力高时,GC 会自动收集。垃圾收集是昂贵的。因此,在处理此类对象的任何时候触发它,不仅是为了释放对象本身,而且是为了释放进程中所有不需要的对象,可能会对应用程序性能产生负面影响。此外,它还违反了单一职责原则,因为 ExtractionService 不应关注进程内存管理。
  • 这确实是特定于应用程序的,但当前设计的问题是,为前一个任务分配的内存确实会阻止下一个任务完成作业,因为数据一次性进入会导致巨大的内存被占用,需要移除。但是让我更多地搜索一下 GC.Collect 的副作用,谢谢。
  • 我会说你不应该删除终结器中的文件,因为它会对你的应用程序的性能产生负面影响,终结器应该很快,所以绝对不应该在终结期间执行。但是如果由于某种原因Dispose 未被调用,文件将不会被删除。但是你也不能依赖终结器,所以我认为最好的方法是删除 Dispose(true) 中的文件并为特殊情况制定备份策略(例如,如果你保证只有一个,则在应用程序启动时执行清理应用实例正在运行)。

标签: c# dispose unmanaged


【解决方案1】:

只需仔细阅读IDisposable 模式即可了解这些“部分”的含义以及何时执行。

TL;DR
您不会在if (disposing) 块之外使用任何 对托管对象的现有引用。在您的示例中,tempFilePaths 似乎是 IEnumerable<string> 类型的字段/属性,一个托管对象(现有引用)。因此,访问/使用它的唯一正确位置是 if (disposing) 块内。

原因
if (disposing) 之外的代码在调用公共Dispose() 方法和被终结器调用时运行both

终结器将在一个特殊的终结器线程上调用。不能保证 - 在终结器调用时 - tempFilePaths 将完全可用。尽管对象引用由您的类实例持有,但它可能已经被 GC 收集了。这就是终结器的工作方式。

如果您在与您的团队讨论时需要更多背景知识和更多论据,请仔细阅读 Eric Lippert(前 C# 编译器团队成员)关于终结器的这些博客文章:

When everything you know is wrong, part one
When everything you know is wrong, part two

【讨论】:

  • OP要删除文件,他不处置tempFilePaths
  • @GuruStron,OP 访问 tempFilePaths 集合已经很危险了。集合内容发生什么并不重要。简单地访问死对象可能会导致终结器崩溃(操作系统会立即终止应用程序)。
  • 是的。如果Dispose 未被调用,则仍然存在未清除文件的问题。
  • 确实如此。但是使用Dispose清理非一次性资源是不正确的设计。
  • @dymanoid,您将哪个资源称为非一次性资源?而如果Dispose位置不对,清理非一次性资源的代码应该写在哪里呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-28
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-04
相关资源
最近更新 更多