2014-06-18 25 views
2

我有以下記錄器狀類模式:抑制流的過早終結在.NET

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"); 
} 

的代碼引發(非確定性) ,它們都是在.net和mono上的,ObjectDisposedException,由於它試圖將緩衝區刷新到已經處置的stream而由對象writer的方法Close引起。

據我所知,原因是GCwriter之前完成stream。我如何更改班級模式以確保stream未在writer之前處理?

我累了在構造函數中使用GC.SuppressFinalize(writer),但我不確定它是不是太hacky。

編輯:


我想,以解決擺在首位有終結的問題。正如在問題開始時提到的那樣,該類用作記錄器,並且我想確保在關閉過程之前將writer中的所有行都刷新到硬盤中。

+0

爲什麼你需要保持流暢? –

+0

只需刪除析構函數。那些不是非託管資源,他們有自己的終結者。 –

+0

我編輯了這個問題,以明確爲什麼有一個終結者。 – houen

回答

3

不要創建終結器,除非您的IDisposable實現真正適用於非託管資源。 FileStreamStreamWriter是受管資源。

此外,由於SafeHandle被引入,很難想象用例,當任何非託管資源不能被包裝到託管SafeHandle。不要盲目追蹤MSDN上的IDisposable實現 - 沒關係,但它適用於這種情況,當你的類型同時運行託管和非託管資源時。

刪除終結所有,並從Dispose扔掉GC.SuppressFinalize(this);

public void Dispose() 
{ 
    lock (disposableLock) 
    { 
     if (!isDisposed) 
     { 
      writer.Close(); 
      stream.Close(); 
      isDisposed = true; 
     } 
    } 
} 

更新

終結器適用於非託管資源清理。
您可以考慮使用終結器作爲關閉文件句柄,網絡套接字等的地方。這不適用於任何應用程序邏輯。一般來說,最終確定期間的託管對象處於不可用狀態 - 不能保證其任何IDisposable(如樣本中的streamwriter)尚未完成。

如果您想確保將特定的日誌消息刷新到文件中,請將其寫入並調用writer.Flush()。否則,如果立即刷新不適合您,請確保您在調用應用程序關閉時調用Logger。另外請注意,您無法避免進程終止,所以不要對記錄器太偏執。

+0

'SafeHandle'不適合任何事情;弱平均模式上的一些變化可能需要以'SafeHandle'無法容納的方式使用終結器。即使(尤其是!)那裏,但終結者的對象不應該是公開的;相反,公共對象應該對可終結對象有強烈的參照。 – supercat

+0

我只是想知道是否有一些很好的模式來管理對象組的最終化。當然,最好的解決方案是手工處置記錄器對象,而不依賴終結器。 – houen

1

管理對象不應該在終結器中清理。終結器應該只用於清理非託管資源。

按照以下方式重寫代碼,以便在完成流時處理託管資源。

public static void Main(string[] args) 
{ 
    using (var t = new DisposableClassWithStream()) 
    { 
    t.WriteLine("Test"); 
    } 
} 

而且一定要檢查出Dispose Pattern article on MSDN

+0

清晰簡潔。您必須始終檢查終結器是否必須避免觸摸任何託管資源,因爲它可能已被垃圾收集器刪除,因爲它在刪除對象時不遵循任何順序。 –

0

當一個終結器運行,最設法其持有的引用將滿足下列條件之一的對象:

-1-不在乎清理,在這種情況下,終結者不應該對他們做任何事情。

-2-當在終結器清理的上下文中調用時,無法以線程安全的方式執行清理,在這種情況下,終結器不應該對它們執行任何操作。

-3-已經使用自己的終結器清理了自己,在這種情況下,終結器應該對它們不做任何處理。

-4-有一個Finalize方法尚未運行,但計劃在第一時間運行,在這種情況下,持有該引用的類的終結器應該對它們不做任何處理。

不同的物體會遇到不同的標準,並且可能很難知道某個特定物體可能符合哪些標準,但對於絕大多數物體將符合任何標準,所需處理是相同的:不要終結者對他們沒有做任何事情。

有涉及之類的弱事件,其中終結也許可以做一些與管理對象有用的一些罕見的情況,但在幾乎所有這些情況下,它應該有終結的唯一類是那些其唯一目的是管理其他密切相關對象的定案清理工作。如果一個人不能理解最終化的所有皺紋,包括短和長的弱引用之間的差異,以及它們如何與終結器交互,那麼任何一個嘗試寫入的終結者都可能會造成更多的傷害而不是更好。