2016-01-20 38 views
1

我有一個內部管理IBuffer的類。數據是敏感的,所以我想讓這個類確保緩衝區在被銷燬之前被清空,以避免將這些位留在內存中。我有一個擦除()方法,該方法如下:在析構函數中刪除IBuffers

public static void Erase(this IBuffer value) 
{ 
    using (var writer = new DataWriter(value.AsStream().AsOutputStream())) 
    { 
     for (int i = 0; i < value.Length; i++) 
      writer.WriteByte(0); 

     var storeTask = Task.Run(async() => await writer.StoreAsync()); 
     storeTask.Wait(); 
    } 
} 

首先,我承認,我的使用Task.Run這裏來調用非CPU綁定的異步方法是犯罪嫌疑人,但我還沒有找到一個同步等價物。替代品是受歡迎的。

我遇到的問題是,在調試模式下,大多數時候在發佈模式下,它運行完美。然而,偶爾在發佈模式,我碰到一個異常時,最後確定()是我的對象上運行:

型「System.Runtime.InteropServices.InvalidComObjectException」未處理的異常發生在System.Runtime.WindowsRuntime。 dll

附加信息:Excep_InvalidComObject_NoRCW_Wrapper。欲瞭解更多信息,請訪問:http://go.microsoft.com/fwlink/?LinkId=623485

的URL真的只對優化爲淨本土的,不是這個特定的異常類型的異常會談。

我推測原因可能與IBuffer在我的Erase方法有機會完成之前被銷燬有關。

我該如何正確實現我想要的行爲?

+1

使用'IDisposable',而不是終結器。 – SLaks

+0

但是不要求類的用戶明確調用Dispose?這不是我想要的行爲。並且IDisposable的文檔說「如果消費者忘記調用Dispose,您應該重寫Object.Finalize以釋放非託管資源。」其中Object.Finalize在C#中通過實現析構函數被重寫。 –

+2

如果你的班級處理不應該留在內存中的祕密,你的調用者需要顯式調用Dispose(通過'using')。 **不要依賴GC來破壞記憶**。 – SLaks

回答

1

雖然術語「析構函數」有時用於描述它們,但C#終結器不像C++析構函數。 C#終結器不保證完全運行。如果終結器確實運行,它沒有定義它相對於其他終結器運行的順序,所以終結器不能依賴訪問它們本身具有終結器的對象(例如COM對象包裝器)。

所以,完全可能的是,如果您依賴終結器並且終結器試圖使用本身可能具有終結器的對象,那麼您可能會發現當終結器運行時,它嘗試使用的對象可能已經具有被清理乾淨了。

確實,如果你實現了Dispose(),一種策略是實現一個終結器(另一個是使用一個SafeHandle子類來包裝非託管資源)。但這只是一個支撐,因爲終結者不能保證運行,所以它不是100%可靠的。實現一個終結器的指導並不是因爲這是一個100%可靠的清理方法,而是因爲如果你正在處理那些忘記叫Dispose()的bug的客戶端代碼,它將是最接近你的。


所以,是&hellip;在C#這裏的正確策略是實現IDisposable,並要求所需的內存清理安全,以確保他們遵守規則,並呼籲Dispose()當它們與對象進行客戶。


順便說一句,只要你的Task.Run()去...&hellip;

您應該異步使用異步方法。例如。不要執行Erase(),而是執行EraseAsync()並使用await裏面。

但是,如果您確實堅持等待他們,則無需使用Task.Run()以匿名方式async執行呼叫。這是主要的矯枉過正。只需等待由StoreAsync()返回的任務對象即可。你可以這樣做,例如通過調用它的GetResults()方法(應該阻塞,直到結果實際可用),或者您可以使用AsTask() extension method直接轉換爲Task<T>對象,然後等待該任務對象。

+0

謝謝,這給了我一些很好的背景。我也有一個正確的'EraseAsync()',主要是我有幾個方法,其中級聯異步/等待會有破壞性。但回過頭來看,我甚至不確定爲什麼我試圖首先在'Task.Run()'中包裝等待的呼叫! –