我有类似记录器的类模式:
public class DisposableClassWithStream : IDisposable
{
public DisposableClassWithStream()
{
stream = new FileStream("/tmp/file", FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
writer = new StreamWriter(stream);
}
public void WriteLine(string s)
{
writer.WriteLine(s);
}
~DisposableClassWithStream()
{
Dispose();
}
private readonly object disposableLock = new object();
private bool isDisposed;
public void Dispose()
{
lock (disposableLock)
{
if (!isDisposed)
{
writer.Close();
stream.Close();
GC.SuppressFinalize(this);
isDisposed = true;
}
}
}
private FileStream stream;
private TextWriter writer;
}
和使用此类的非常简单的代码:
public static void Main(string[] args)
{
var t = new DisposableClassWithStream();
t.WriteLine("Test");
}
由于对象ObjectDisposedException
的方法Close
导致的.net和单声道writer
导致代码抛出(非确定性),因为它尝试将缓冲区刷新到stream
已被处置。
我理解原因是GC
在stream
之前完成writer
。如何更改班级模式以确保stream
之前没有处置writer
?
我厌倦了在构造函数中使用GC.SuppressFinalize(writer)
,但我不确定它是不是太过于hacky。
修改
我想首先解决终结者的问题。如问题开头所述,该类用作记录器,我想确保在关闭进程之前将writer
中的所有行刷新到硬盘上。
答案 0 :(得分:3)
不要创建终结器,除非您的IDisposable
实现真正适用于非托管资源。 FileStream
和StreamWriter
是受管资源。
此外,自从引入SafeHandle
以来,很难想象用例,当任何非托管资源无法包装到托管SafeHandle
中时。不要盲目地跟踪MSDN中的IDisposable
实现 - 它没关系,但它适用于这种情况,当你的类型同时运行托管和非托管资源时。
删除终结器,然后从GC.SuppressFinalize(this);
:
Dispose
public void Dispose()
{
lock (disposableLock)
{
if (!isDisposed)
{
writer.Close();
stream.Close();
isDisposed = true;
}
}
}
<强>更新强>
终结器用于非托管资源清理
您可以将终结器视为关闭文件句柄,网络套接字等的位置。这不适用于任何应用程序逻辑。通常,在最终确定期间,托管对象处于不可用状态 - 无法保证其任何IDisposable
(如样本中的stream
或writer
)尚未最终确定已经
如果您想确定,将该特定日志消息刷新到文件中,而不是写入并调用writer.Flush()
。否则,如果您不希望立即进行刷新,请确保在应用程序关闭时您正在为记录器调用dispose。另请注意,您无法防止进程终止,因此不要对您的记录器过于偏执。
答案 1 :(得分:1)
不应在终结器中清理托管对象。终结器应仅用于清理非托管资源。
在完成流后,重写您的代码,以便处理托管资源。
public static void Main(string[] args)
{
using (var t = new DisposableClassWithStream())
{
t.WriteLine("Test");
}
}
答案 2 :(得分:0)
当终结器运行时,它保存引用的大多数托管对象将满足以下条件之一:
-1-不关心清理,在这种情况下,终结器不应该对它们做任何事情。
-2-在终结器清理的上下文中调用时,无法以线程安全的方式执行清理,在这种情况下,终结器不应对它们执行任何操作。
-3-已经使用自己的终结器清理了自己,在这种情况下,终结器不应该对它们做任何事情。
-4-有一个Finalize
方法尚未运行,但计划在第一次运行时运行,在这种情况下,持有引用的类的终结器不应对它们执行任何操作。
不同的对象将满足不同的标准,并且可能很难知道特定对象可能满足哪些标准,但是对于绝大多数对象将满足任何标准,所需的处理是相同的:don'在终结器中对它们做任何事情。
有一些罕见的场景涉及弱事件,其中终结器可能能够对托管对象做一些有用的事情,但在几乎所有这些情况下,唯一应该有终结器的类是其唯一目的< / em>用于管理其他与密切相关的对象的最终清理。如果一个人不理解最终确定的所有皱纹,包括短期和长期弱引用之间的差异以及它们如何与终结者进行交互,那么任何试图写入的终结者都可能弊大于利。