Je rencontre des problèmes lors de la gestion correcte de Dispose/Finalization avec un ConcurrentBag
qui contient des objets non gérés. Exécuter le code ci-dessous (généralement) produit un ObjectDisposedException
(Cannot access a disposed object.Object name: 'The ThreadLocal object has been disposed.'.
) sur l'appel à TryTake()
. Vraisemblablement, dans ce cas, le garbage collector détruit ConcurrentBag
avant d'appeler le finaliseur de A. Je pensais que ce ne serait le cas que si le ConcurrentBag
lui-même implémentait un finaliseur. Est-il vrai que l'on ne devrait jamais toucher les objets gérés pendant le processus de finalisation?Finalisation de ConcurrentBag contenant des objets non gérés
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
}
}
}
Trigger l'exception:
void Main()
{
var a = new A(null);
}
Ce qui suit semble contourner ce problème spécifique, mais je ne suis pas sûr si cela est sûr. Y a-t-il une implémentation parfaitement sûre pour ce scénario?
while (_collection.IsEmpty == false)
{
object value;
_collection.TryTake(out value);
// Cleanup value
}
Votre explication est parfaitement logique.Selon l'article de Stephen Cleary, l'implémentation correcte serait d'envelopper les objets non gérés dans les wrappers IDisposable (ce qu'il appelle Level 0) qui gèrent le sale travail de considérer la finalisation. Cela permet au niveau supérieur, ma classe A (c'est-à-dire le niveau 1), de ne s'occuper que du chemin de Dispose et d'ignorer le chemin de finalisation. – Terrence
La seule chose que vous avez incorrecte est que les objets de niveau 0 doivent dériver de 'SafeHandle' non seulement' IDisposeable', mais à part ça, oui. –