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

问题描述:

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

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

@Hans:这不是根本错误;这是完全可能的,多线程希望在它们之间进行谈判谁得到处置对象时,他们都与它在同一时间全部完成。 **谈判协议必须由线程安全的部分组成。** Brian的博客不是“动荡”。 – 2011-03-14 14:55:11

+1

这不是真正的线程安全的,因为它没有正确地解决其他方法调用该类的行为必须如何。如果你真的想在Dispose上拥有线程安全性,最简单的方法是将整个Dispose方法封装在一个锁中,并在每个公共方法周围使用相同的锁,并抛出ObjectDisposedException(如果已经处理)。如果您需要允许方法与Dispose方法并行执行,则可以使用MultiRead/SingleWrite模式,其中Dispose是“写入”操作。 – 2011-03-14 14:56:21

+0

@Eric:如果此协商正确实施,那么它不需要IDisposable实现是线程安全的。谈判需要。我没有断言Brian的博客是顺便说一句。对不起,冒犯了你。 – 2011-03-14 14:59:35

在完成之前,您不应该处置对象。如果有其他线程引用该对象,并且有可能需要调用它的方法,则不应该对其进行处理。

因此,Dispose不是必须的线程安全。

+2

在一些情况下,优选的方式(如果不是唯一的方式)来强制一个阻塞I/O操作的放弃是'Dispose'从它的下面的连接的;例如可处置* *只从外螺纹发生(在阻塞的线程不能做任何事情,而它的阻止)。这是可能的外螺纹可能决定'Dispose'就像连接完成了其工作,并已被阻塞线程的连接决定'Dispose'它本身。 – supercat 2012-06-15 17:32:04

+0

如果有一个用例另一个线程迫使阻塞操作的放弃,这需要有人来编写代码,使其工作。当然实施者将是明智的实现'ForceAbandon'方法拉泽而不是滥用'Dispose'的语义。如果实现者没有意识到这是一个必需的用例,那么你不能指望'Dispose'也能正确运行。它可能,但那只是运气。 – Ben 2012-06-15 18:53:31

+1

我见过很多I/O库,其中唯一支持的跨线程操作正在终止连接。 “处置”似乎是最合适的。此外,在某些情况下,要保证局部构造的对象的清理最合理的方式可能涉及相互处置对方的对象,所以'Object1.Dispose'可以称之为'Object2.Dispose',这可能依次调用'Object1.Dispose '。防止这种情况不需要“互锁”,但它仍然需要比微软更好的模式。 – supercat 2012-06-15 19:42:33

线程安全的Dispose模式的唯一真正好处是您可以保证在发生跨线程误操作时获得ObjectDisposedException而不是潜在的不可预知的行为。请注意,这意味着该模式需要超过线程安全的Dispose;它要求所有依赖不被处置的类的方法与处理机制正确地互锁。

可以做到这一点,但是要覆盖只有在存在使用错误(即错误)时才会发生的边界情况是很费事的。

+0

关于所涉及的努力:我们可以使用PostSharp将验证逻辑注入每个公共方法吗?我在看PostSharp的方向的唯一原因是因为我看到其他语言一直在做这类事情。 – GregC 2011-03-25 17:08:57

+0

尽管没有很多情况下多个线程会同时发现一个对象是无用的,但是在很多情况下,对象的最后一次使用可能会异步发生,或者一个对象有资格进行垃圾回收将意味着仍然存在强烈参照的另一个对象已经变得毫无用处。保证线程安全的“IDisposable”在这种情况下会很有用。 – supercat 2012-06-15 17:39:44

我不确定Microsoft为什么不在非虚拟处置方法中使用互锁Disposing标志(打算终结器 - 如果有的话 - 应该使用相同的标志)。多线程可能尝试处理对象的情况很少,但并不禁止。例如,可能会发生这样的对象,它们应该执行一些异步任务并自行清理,但如果需要,可以尽早将其清除。对象处理不应该经常发生,因为Interlocked.Exchange有任何有意义的性能成本。

。另一方面,需要注意的是,同时保护对处置多次调用是一个恕我直言明智的政策是非常重要的,它是不足以使处置真的是线程安全的。还有必要确保在正在使用的对象上调用Dispose会使事情处于良好状态。有时最好的模式是设置一个“KillMeNow”标志,然后在由Monitor.TryEnter守护的块中,处理该对象。它使用该对象必须既获取之前和解除锁定,以查看是否KillMeNow被设定后,即可获得操作期间的锁,但是测试每例程(比其它处置);如果是这样,请执行Monitor.TryEnter并执行处理逻辑。

与进行线程安全的IDisposable更大的问题是,微软并没有指定一个事件的RemoveHandler方法必须是线程安全的了无死锁的风险的事实。 IDisposable.Dispose经常需要删除事件处理程序;如果没有这种保证线程安全的方式,写一个线程安全的Dispose几乎是不可能的。