我在使用包含非托管对象的ConcurrentBag
正确处理Dispose / Finalization时遇到问题。运行以下代码(通常)会在ObjectDisposedException
的调用中产生Cannot access a disposed object.Object name: 'The ThreadLocal object has been disposed.'.
(TryTake()
)。
据推测,在这种情况下,垃圾收集器在调用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
}
答案 0 :(得分:3)
当从终结器执行代码时(disposing
是false
),您可以做的唯一事情是使用没有状态的静态方法,函数本地的变量以及继承的字段CriticalFinalizerObject
(除非您在CriticalFinalizerObject
的终结器中,否则您无法使用它们。)
由于ConcurrentBag
未从CriticalFinalizerObject
继承,因此您无法在运行自己的终结器时依赖它。 this
和_collection.m_locals
变量Sign mentions in his answer都会在this
无法访问的同时进入终结队列。处理队列的顺序不确定。
有一篇很棒的文章&#34; IDisposable: What Your Mother Never Told You About Resource Deallocation&#34;深入了解当somthing最终确定时实际发生的事情,并提供比微软推荐的传统private void Dispose(bool disposing)
模式更好的模式。
答案 1 :(得分: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表明它具有IDisposable ThreadLocal
,并在GetThreadList
方法中使用。奇怪的是,如果你使用一个普通的旧foreach
循环,你可以避免使用ThreadLocal
,看起来一切都按照你期望的方式工作。
foreach (var value in _collection)
{
// Cleanup value
}
尽管有这样的挖掘,我还没有解释ThreadLocal
如何处置。