何时处理以及为什么处理?

11

我在这个方法上提出了一个问题

// Save an object out to the disk
public static void SerializeObject<T>(this T toSerialize, String filename)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
    TextWriter textWriter = new StreamWriter(filename);

    xmlSerializer.Serialize(textWriter, toSerialize);
    textWriter.Close();
}

在我收到的响应中,我得到了以下的附加备注:

确保您始终处理可释放资源,例如流和文本读写器。这似乎不是您的SerializeObject方法的情况。

所以,对于已经编写C#一两年的人来说,我可以告诉你这似乎很无聊,但我为什么要处理它呢?

我看到testWriter有一个dispose方法,但难道垃圾回收不会处理吗?我从Delphi转到了C#。在Delphi中,我必须清理所有内容,所以这不是因为我想偷懒。我只是被告知如果强制释放内存,可能会导致一些问题。我被告知“让垃圾回收器去处理它”。

  1. 那么,为什么我需要调用dispose方法呢?(我的猜测是因为textWriter会打印到磁盘上。)
  2. 是否有一个需要小心处理的对象列表?(或者有一种简单的方法可以知道何时需要调用dispose方法?)

1
相关:https://dev59.com/TnI-5IYBdhLWcg3wwLW3 - Jon B
我的理解是它让垃圾收集器知道这个对象已经准备好回收,而不必检查它是否已准备好。此外,您可以告诉垃圾收集器提前回收此项,而不是框架暂时保留它“以防万一”。http://www.devx.com/dotnet/Article/33167 - jcolebrand
4
@drachenstern - 这是一种常见的误解,但是是不正确的。Dispose仅仅为你提供了一种机制,可以在你使用完非托管资源如文件句柄或网络套接字后立即强制关闭它们,而不必等待垃圾回收器。它并不会向GC发出信号来回收对象。 - Paolo
我看到了很多好的答案,但是我认为这个问题被忽略了:如果调用了 Close,那么为什么还需要调用 Dispose - user166390
@Paolo ~ 谢谢。你看,我也是来学习的!;)... 我不知道它不会帮助GC。这似乎是一个小优化的好地方,但我不是框架的作者。 - jcolebrand
12个回答

15
这里有一个简单的经验法则:对于实现了IDisposable接口的对象,总是要调用Dispose()方法(并不是所有对象都实现了该接口)。你并不总是知道为什么对象需要实现Dispose,但你应该假定它存在的原因。
最简单的确保这一点的方法是使用using语句块:
using (TextWriter tw = new StreamWriter(fileName))
{
   // your code here
}

这将在使用块结束时自动调用Dispose()(本质上与在finally块中使用try/catch/finally并调用Dispose()相同)。

有关Dispose如何与垃圾收集一起使用的更多信息,请参见此处


12

您说得对,对于编写良好的代码,垃圾回收器最终会清理本地资源。对象将具有终结器,并在终结期间释放必要的本地资源。

然而,这个清理发生的时间非常不确定。另外,这有点前后矛盾,因为您正在使用设计用于处理托管内存的GC来管理本地资源。这会导致有趣的情况,并且可能导致本地资源保持活动状态的时间比预期的要长,从而导致以下情况:

  • 文件在不再使用之后仍然打开
  • 资源句柄可能耗尽,因为GC没有看到足够的内存压力来强制进行收集,从而运行终结器

使用/释放模式为本地资源的清理添加了确定性,并消除了这些问题。


4

如果你知道你不会再使用某个资源,可以自行处理它;你肯定比垃圾回收器更快,这样可以让其他人更快地使用文件或者你打开的任何其他资源。最简单的方法是使用你的TextWriter或者任何其他资源在using语句中:

using (TextWriter textWriter = new StreamWriter(filename))
{
    xmlSerializer.Serialize(textWriter, toSerialize);
}

这基本上确保TextWriter在结尾处被处理。不管怎样,你也不需要再使用它了。

3
可能不必同时执行Close和Dispose。 - Conrad Frix
@Conrad 是的,我知道。我基本上保留了他的代码,只是为了向他展示他需要改变的很少。 - alex

4
垃圾收集器会释放所有资源,但是它执行的时间是不确定的。Dispose方法提供了一种立即释放非托管资源的方式。

垃圾回收器释放所有资源。注意:如果对象Bob已经订阅了某个长时间存在的对象(例如AppDomain)的事件,则除非你告诉它取消订阅,否则GC永远不会清理Bob! - Niki
没错,GC只是调用终结器,释放资源的责任在于类开发者在Dispose/finalizer中进行。我所说的是使用.NET中可释放类,比如问题中的TextWriter,这些类(希望)都是编写得当的。 - Alex F

3

实际上,由于TextWriter.Close方法已经处理了它,所以您已经在处理它。

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

所以您可以将您的代码更改为。这个
public static void SerializeObject<T>(this T toSerialize, String filename)
{
    TextWriter textWriter;
    try
    {
         XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
         textWriter = new StreamWriter(filename);

          xmlSerializer.Serialize(textWriter, toSerialize);
    }
    finally
   {
       textWriter.Close();
   }

这与其他答案中的using()非常相似。

不执行此操作的影响是,如果Serialize出现错误,则Framework需要等待一段时间才能放弃其文件锁(当处理fReachable队列时)。

我知道FxCop会告诉你何时实现IDisposable,但我认为除了查看文档并查看对象是否实现了IDisposable(或智能感知),没有其他简单的方法可以找出何时需要调用Dispose。


2

如果您正在使用本地资源(例如文件句柄),则应该调用Dispose()尽快关闭它们,而不是等待GC运行(这可能会在更高的gc代中迟得多)。并且您要关闭文件,因为文件访问通常以某种方式锁定文件。


1

如果您正在打开资源(例如文件或数据库连接),则处理该资源将释放其对资源的控制。如果您不这样做,那么其他人可能无法连接到数据库或使用文件。

一般来说...如果类实现了IDisposable接口,则在完成时应调用Dispose()方法。很可能他们之所以使它可处理是有原因的 :)


1

TextWriter.Dispose 文档中:

注意 在释放对 TextWriter 的最后引用之前,始终调用 Dispose。否则,它正在使用的资源直到垃圾回收器调用 TextWriter 对象的 Finalize 方法时才会被释放。

Object.Finalize 文档中:

在垃圾回收期间执行终结器的确切时间是未定义的。除非调用 Close 方法或 Dispose 方法,否则不能保证资源会在任何特定时间释放。

Finalize方法在以下异常情况下可能无法完全运行或根本无法运行:
- 另一个终结器无限期地阻塞(进入无限循环,尝试获取永远无法获取的锁等)。因为运行时尝试运行终结器以完成,如果终结器无限期地阻塞,则可能不会调用其他终结器。 - 进程在没有给运行时清理的机会的情况下终止。在这种情况下,运行时对进程终止的第一个通知是DLL_PROCESS_DETACH通知。
只有在可终结对象的数量继续减少时,运行时才会在关闭期间继续终结对象。

0

垃圾回收器确实会“处理”它。迟早会处理。当它在下一次垃圾回收后调用终结器时。

调用Dispose可以确保资源(如文件句柄)尽快释放,从而使它们可供系统中的其他进程重复使用。大多数情况下,您不会注意到自己调用Dispose和让垃圾回收器处理之间的区别。但有时候你会。例如,创建大量HttpWebResponse对象的Web爬虫,如果您在使用后不将其处理掉,将很快耗尽连接。(我没有遇到过这个问题...不是我。)


0
调用Dispose而不是等待GC的原因通常是因为您正在使用有限的系统资源,您希望尽快释放它,以便其他进程或线程可以使用它。对于具有IDisposable模式的对象,"using"结构是一种简单易读的方式,可以确保调用Dispose。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接