2016-08-12 21 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'继承。 –

相关问题