为什么我没有看到IDisposable实现并发的任何实现?

时间:2011-03-14 14:17:04

标签: c# thread-safety idisposable

当我查看IDisposable的示例实现时,我没有找到任何线程安全的。为什么IDisposable没有实现线程安全? (相反,调用者有责任确保只有一个线程调用Dispose())。

5 个答案:

答案 0 :(得分:30)

在完成对象之前,不应丢弃对象。如果有其他线程引用该对象,并且他们可能想要调用它的方法,那么就不应该处理它。

因此,Dispose不一定是线程安全的。

答案 1 :(得分:6)

线程安全的Dispose模式的唯一真正好处是可以保证在跨线程滥用的情况下获得ObjectDisposedException而不是潜在的不可预测的行为。请注意,这意味着该模式需要的不仅仅是线程安全的Dispose;它要求所有依赖于未处置的类的方法与处理机制正确联锁。

可以这样做,但要覆盖仅在出现使用错误(即错误)时才会出现的边界情况需要付出很多努力。

答案 2 :(得分:2)

Brian Lambert撰写了一篇名为 A simple and totally thread-safe implementation of IDisposable 的博客文章 它包含以下实现:

using System;
using System.Threading;

/// <summary>
/// DisposableBase class. Represents an implementation of the IDisposable interface.
/// </summary>
public abstract class DisposableBase : IDisposable
{
    /// <summary>
    /// A value which indicates the disposable state. 0 indicates undisposed, 1 indicates disposing
    /// or disposed.
    /// </summary>
    private int disposableState;

    /// <summary>
    /// Finalizes an instance of the DisposableBase class.
    /// </summary>
    ~DisposableBase()
    {
        // The destructor has been called as a result of finalization, indicating that the object
        // was not disposed of using the Dispose() method. In this case, call the DisposeResources
        // method with the disposeManagedResources flag set to false, indicating that derived classes
        // may only release unmanaged resources.
        this.DisposeResources(false);
    }

    /// <summary>
    /// Gets a value indicating whether the object is undisposed.
    /// </summary>
    public bool IsUndisposed
    {
        get
        {
            return Thread.VolatileRead(ref this.disposableState) == 0;
        }
    }

    #region IDisposable Members

    /// <summary>
    /// Performs application-defined tasks associated with disposing of resources.
    /// </summary>
    public void Dispose()
    {
        // Attempt to move the disposable state from 0 to 1. If successful, we can be assured that
        // this thread is the first thread to do so, and can safely dispose of the object.
        if (Interlocked.CompareExchange(ref this.disposableState, 1, 0) == 0)
        {
            // Call the DisposeResources method with the disposeManagedResources flag set to true, indicating
            // that derived classes may release unmanaged resources and dispose of managed resources.
            this.DisposeResources(true);

            // Suppress finalization of this object (remove it from the finalization queue and
            // prevent the destructor from being called).
            GC.SuppressFinalize(this);
        }
    }

    #endregion IDisposable Members

    /// <summary>
    /// Dispose resources. Override this method in derived classes. Unmanaged resources should always be released
    /// when this method is called. Managed resources may only be disposed of if disposeManagedResources is true.
    /// </summary>
    /// <param name="disposeManagedResources">A value which indicates whether managed resources may be disposed of.</param>
    protected abstract void DisposeResources(bool disposeManagedResources);
}

然而,在博客和此处的评论中,绝对和完整的总体性存在争议。

答案 3 :(得分:1)

我不确定微软为什么不在非虚拟处理方法中使用互锁的Disposing标志(意图是终结器 - 如果有的话 - 应该使用相同的标志)。多线程可能尝试处理对象的情况很少见,但并不是禁止的。例如,它可能发生在应该执行某些异步任务并在其自身之后进行清理的对象,但如果需要可以在早期将其杀死。对象处理不应经常发生,以使Interlocked.Exchange具有任何有意义的性能成本。

另一方面,重要的是要注意虽然保护Dispose免受多次调用是恕我直言的明智政策,但是使Dispose真的是线程安全是不够的。还必须确保在正在使用的对象上调用Dispose将使事物处于良好状态。有时最好的模式是让dispose设置一个“KillMeNow”标志,然后在Monitor.TryEnter保护的一个块中,Dispose对象。使用该对象的每个例程(Dispose除外)必须在操作期间获取锁,但在获取之前和释放锁之后都要测试是否设置了KillMeNow;如果是这样,请执行Monitor.TryEnter并执行dispose逻辑。

制作线程安全IDisposable的一个更大问题是,Microsoft没有指定事件的RemoveHandler方法必须是线程安全的,没有死锁风险。 IDisposable.Dispose经常需要删除事件处理程序;如果没有保证线程安全的方法,那么编写一个线程安全的Dispose几乎是不可能的。

答案 4 :(得分:0)

因为它们是错误的和懒惰的,经常被认为是“边际案例”或“不值得的开销”。

使用读取器/写入器锁同步一次性课程。您要防止并发写入的是处于“已处置”状态。所有方法都只需获取一个读取锁即可使用该类,从而确保它们不会在调用过程中被丢弃。因为支持多个阅读器,所以方法不会相互阻塞。 “ dispose”方法应获取writer锁以执行清理,以确保它不会与任何其他方法或自身并发运行。

这为该类中的dispose方法和所有其他方法提供了完全的线程安全性。处置逻辑将不会在正常逻辑运行时运行(反之亦然),也不会与自身并发运行。

您使用的读写器锁应支持异步,因此您可以使用异步代码。另外,异步锁通常不支持重入。这很好,因为您无论如何都不希望一个方法调用另一个方法,因为在两个读取锁定获取调用之间可能会发生“写入器”锁定,并防止无限期地取出第二个读取锁定(死锁)。

如果您愿意遵循“正确使用该类,直到确定您已完成处理后再进行处置”的思路,那么这全都是额外的开销。但是,我相信这是唯一真正正确的,完全线程安全的方法。