包含非托管对象的 ConcurrentBag 的终结
Finalization of ConcurrentBag containing unmanaged objects
我在使用包含非托管对象的 ConcurrentBag
正确处理 Dispose/Finalization 时遇到问题。 运行 下面的代码(通常)会在调用 TryTake()
.
时生成 ObjectDisposedException
(Cannot access a disposed object.Object name: 'The ThreadLocal object has been disposed.'.
)
大概在这种情况下,垃圾收集器在调用 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
}
对象处置异常的完整堆栈跟踪是
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
是如何处理的。
当代码从终结器执行时(disposing
是 false
),您唯一可以做的事情是使用没有状态的静态方法、函数局部变量和字段从 CriticalFinalizerObject
继承(除非你在 CriticalFinalizerObject
的终结器中,否则你不能使用它们)。
因为 ConcurrentBag
没有继承自 CriticalFinalizerObject
,所以当你自己的终结器是 运行 时,你不能指望它没有被终结。当 this
变得不可访问时,this
和 _collection.m_locals
变量 都同时被放入终结队列。处理队列的顺序是不确定的。
有一篇很棒的文章“IDisposable: What Your Mother Never Told You About Resource Deallocation”深入探讨了某物最终确定时实际发生的情况,并提供了一些比 Microsoft 推荐的传统 private void Dispose(bool disposing)
模式更好的模式。
我在使用包含非托管对象的 ConcurrentBag
正确处理 Dispose/Finalization 时遇到问题。 运行 下面的代码(通常)会在调用 TryTake()
.
ObjectDisposedException
(Cannot access a disposed object.Object name: 'The ThreadLocal object has been disposed.'.
)
大概在这种情况下,垃圾收集器在调用 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
}
对象处置异常的完整堆栈跟踪是
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
是如何处理的。
当代码从终结器执行时(disposing
是 false
),您唯一可以做的事情是使用没有状态的静态方法、函数局部变量和字段从 CriticalFinalizerObject
继承(除非你在 CriticalFinalizerObject
的终结器中,否则你不能使用它们)。
因为 ConcurrentBag
没有继承自 CriticalFinalizerObject
,所以当你自己的终结器是 运行 时,你不能指望它没有被终结。当 this
变得不可访问时,this
和 _collection.m_locals
变量
有一篇很棒的文章“IDisposable: What Your Mother Never Told You About Resource Deallocation”深入探讨了某物最终确定时实际发生的情况,并提供了一些比 Microsoft 推荐的传统 private void Dispose(bool disposing)
模式更好的模式。