在析构函数中访问ConcurrentDictionary

时间:2015-03-19 03:00:00

标签: c# .net multithreading concurrency

我有一个Wrapper<T> where T : class包裹着我的物体。我将WeakReference<Wrapper<T>>存储在ConcurrentDictionary中,为不可变对象实现弱引用的线程安全缓存,当其他内容需要内存时,它们会自动清理。我需要在ConcurrentDictionary.TryRemove析构函数中调用Wrapper来释放字典中不再指向有效对象的弱引用。

众所周知,由于存在死锁的风险,我们不应在析构函数内部使用任何锁定。所以我想知道,我可以在析构函数中安全地使用ConcurrentDictionary.TryRemove吗?我担心它可能是使用SpinLock或其他工具实现的,因此在析构函数中使用时仍然存在死锁的风险。

3 个答案:

答案 0 :(得分:1)

您可以在此location看到ConcurrentDictionary的实现,而TryRemove实现使用'lock(...)'。

在析构函数中你可以做的是使用线程池来执行从字典中删除项目。您仍然需要将包装器实例标记为不再有效,这样如果在运行的终结器和删除它的线程池之间调用其任何公共方法,您可以检测到并拒绝该调用。

答案 1 :(得分:0)

所以 DON&#39; T 想要在析构函数中使用锁定的原因是因为析构函数可能会被一个sperate线程中的FinalizerWorker调用,同时在所有线程上停止执行

因此,如果一个线程在FinalizerWorker启动时处于ConcurrencyDicitonary操作的中间,那么如果析构函数试图锁定ConcurrencyDictionary,则可能会死锁(这可能很难重现死锁)。

旋转锁不会帮助你,因为如果任何当前正在执行的线程已将ConcurrencyDictionary锁定或Spinner变量被锁定,将不会释放它,直到FinalizerWorker完成,这不会因为它将旋转/锁定永远。

这里的主要选项是使用SuppressFinalize(this)调用来实现IDisposable接口,因为你的对象将禁止Finalizer工作器,不会发生死锁,并且ConcurrencyDictionary操作是安全的!

因此,如果您使用object.Dispose()抢占Finalizer,您应该安全使用ConcurrencyDictionary,否则不要在Finalizer Dispose(false)调用中使用任何类型的锁定,否则您将在某些时候死锁。

// Design pattern for a base class.
public class Base: IDisposable
{
    private bool disposed = false;
//Implement IDisposable.
public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
    if (!disposed)
    {
        //Disposing outside the FinalizerWorker (SAFE)
        if (disposing)            
            m_pDictionary.TryRemove(this);            

        disposed = true;
    }
}

// Use C# destructor syntax for finalization code.
~Base()
{
    // Simply call Dispose(false).
    Dispose (false);
}

答案 2 :(得分:0)

这里的答案让你误入歧途。不鼓励在析构函数/终结器中使用锁,因为它很容易导致死锁,尤其是在“手动”而不是使用并发集合的情况下实现时,但有时这是必要的。即使是“停止世界”,GC实现终结器也会在一个单独的终结器线程上运行,该线程与您的应用程序同时发生。

首先是第一件事 - 非常罕见,你所建议的是实现你想要的功能的理想方式,我非常有信心它不是。首先,WeakReferences不适合用于缓存,因为它们的收集频率远远超过“需要内存”时的收集。适当的缓存实现使用强引用监视内存级别,并在内存使用率过高时根据需要释放它们。

即使在像WeakValueDictionary这样的实现中,如果可以收集该集合,您也不希望该集合保留该值,但该实现仍然不会收到对象集合通知。相反,只要您偶然发现收集的一个条目,或者每隔X次操作或每隔Y秒扫描整个集合中的死条目等,就可以删除该条目。

也就是说,如果遇到收集对象时需要做某事的情况,可以使用并发集合。

假设您没有做任何愚蠢的事情,在通知队列中排队ID或从并发字典中删除项目是安全的,因为这些操作很快并且只在很短的时间内阻止而您的应用程序不是在终结者运行时被阻止。

这是你应该尽可能避免的事情,但有时它是实现某些事情的最好和唯一的方法。只需确保锁定速度快,并尽可能少地使用,而不是任何容易意外出错和死锁的多级锁定方案的一部分。