2016-08-12 22 views
2

我無法正確處理包含非託管對象的ConcurrentBag的Dispose/Finalization。運行下面的代碼(通常)會在TryTake()的呼叫中生成ObjectDisposedExceptionCannot access a disposed object.Object name: 'The ThreadLocal object has been disposed.'.)。包含非託管對象的ConcurrentBag的終結

大概在這種情況下,垃圾收集器在調用A的終結器之前破壞了ConcurrentBag。我以爲只有在ConcurrentBag本身實現了終結器的情況下才會這樣。是否在終止路徑期間不應該觸摸託管對象?

class A : IDisposable 
{ 
    private readonly ConcurrentBag<object> _collection = new ConcurrentBag<object>(); 

    public A(string value) 
    { 
     if (value == null) throw new ArgumentNullException(); 
    } 

    ~A() 
    { 
     Dispose(false); 
    } 

    public void Dispose() => Dispose(true); 

    private void Dispose(bool disposing) 
    { 
     if (disposing) {} 

     object value; 
     while (_collection.TryTake(out value)) 
     { 
      // Cleanup value 
     } 
    } 
} 

觸發異常:

void Main() 
{ 
    var a = new A(null); 
} 

下似乎解決這一具體問題,但我不能確定這是否是安全。這種情況下是否有完全安全的實現?

while (_collection.IsEmpty == false) 
{ 
    object value; 
    _collection.TryTake(out value); 
    // Cleanup value 
} 

回答

2

當代碼從終結(disposingfalse)你被允許做的是使用無狀態函數的局部靜態方法,變量,從CriticalFinalizerObject繼承(除非你是在一個唯一的東西,和字段執行終結者爲CriticalFinalizerObject然後你不能使用它們)。

因爲ConcurrentBag沒有從CriticalFinalizerObject繼承,所以當你自己的終結器運行時,你不能依賴它沒有被最終確定。 this_collection.m_locals變量Sign mentions in his answer都會在this變得無法訪問的同時進入終結隊列。隊列處理的順序不是確定性的。

有一篇偉大的文章「IDisposable: What Your Mother Never Told You About Resource Deallocation」深入探討了當somthing完成時實際發生的事情,並提供了一些比Microsoft推薦的傳統private void Dispose(bool disposing)模式更好的模式。

+0

你的解釋很有道理。 基於Stephen Cleary的文章,正確的實現是將非託管對象包裝在IDisposable包裝中(他稱爲「級別0」),該包裝處理考慮完成的骯髒工作。這允許更高級別的我的類A(即級別1)類只關心處理路徑並忽略最終化路徑。 – Terrence

+0

唯一不正確的是0級對象應該從'SafeHandle'派生出來,而不是'IDisposeable',但除此之外,是的。 –

1

的對象的完整堆棧跟蹤設置的例外是

at System.Threading.ThreadLocal`1.GetValueSlow() 
    at System.Threading.ThreadLocal`1.get_Value() 
    at System.Collections.Concurrent.ConcurrentBag`1.GetThreadList(Boolean forceCreate) 
    at System.Collections.Concurrent.ConcurrentBag`1.TryTakeOrPeek(T& result, Boolean take) 
    at System.Collections.Concurrent.ConcurrentBag`1.TryTake(T& result) 
    at A.Dispose(Boolean disposing) 
    at A.Finalize() 

這意味着,所設置的對象住內部ConcurrentBag這是因爲奇是ConcurrentBag不IDisposable的。挖掘source of ConcurrentBag顯示它有一個ThreadLocal這是IDisposable和用於GetThreadList方法。奇怪的是,如果你使用一個簡單的舊的foreach循環,你可以避開ThreadLocal,它看起來像一切都按照你期望的方式工作。

foreach (var value in _collection) 
{ 
    // Cleanup value 
} 

儘管這樣挖我沒有爲ThreadLocal是怎麼佈置的解釋。

+0

它被處置,因爲當'a'變得無法到達時,'_collection'被認爲無法訪問,這導致無法訪問'_collection.m_locals'並將其放入終結器隊列中。 'm_locals'終結器在'A'的終結器之前運行,因爲'ThreadLocal '不會從'CriticalFinalizerObject'繼承。 –

相關問題