2011-05-15 28 views
7

今天我在玩Lazy <T>,发现一个有趣的案例(在我看来)。懒惰<T>与LazyThreadSafeMode.PublicationOnly和IDisposable

http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx

  • PublicationOnly:

    当多个线程试图同时初始化一个懒惰的情况下,所有的线程被允许运行初始化方法...由创建的的T任何实例竞争线程被丢弃。

    如果我们看一下懒<T> .LazyInitValue()的代码,我们会发现,没有检查的IDisposable的实施和resoruces可能泄漏的位置:

    case LazyThreadSafetyMode.PublicationOnly: 
         boxed = this.CreateValue(); 
         if (Interlocked.CompareExchange(ref this.m_boxed, boxed, null) != null) 
         { 
          //* boxed.Dispose(); -> see below. 
          boxed = (Boxed<T>) this.m_boxed; 
         } 
         break; 
    

截至目前的唯一出路确保只创建实例是使用LazyThreadSafetyMode.ExceptionAndPublication

所以我有2个问题:

  • 我是否错过了什么,或者我们可以看到,一些insntance可以创建和资源可以在这种情况下泄露?
  • 如果这是正确的假设,为什么不能在这种情况下,检查了IDisposable和实施的Dispose()上的盒装<T>使得它代表处置的T盒装来说,如果它实现了IDisposable或以某种不同的方式:

    class Boxed<T> 
        { 
         internal T m_value; 
         void Dispose() 
         { 
          if (m_value is IDisposable) 
          {  ((IDisposable) m_value).Dispose(); } 
         } 
        } 
    
+0

这在.NET上被忽略了,这就是为什么我提供[LazyNeedle ](https://github.com/theraot/Theraot/blob/master/Core/Theraot/Threading/Needles/LazyNeedle.cs)和[ LazyDisposableNeedle ](https://github.com/theraot/Theraot/blob/master/Core/Theraot/Threading/Needles/LazyDisposableNeedle.cs)。顺便说一句,我[懒惰](https://github.com/theraot/Theraot/blob/master/Core/System/Lazy1.net35.cs)的模拟这种行为,(看看右边的码)。 – Theraot 2013-12-16 20:57:06

回答

2

要回答第一个问题,如果一个类实现IDisposable“正确”,那么不应该有资源泄漏的危险。然而,在垃圾收集发生之前,非托管资源可能会延迟释放。

考虑以下应用:

using System; 
using System.Collections.Generic; 
using System.Runtime.InteropServices; 
using System.Threading; 

namespace LazyInit { 
    class DisposableClass : IDisposable { 
     private IntPtr _nativeResource = Marshal.AllocHGlobal(100); 
     private System.IO.MemoryStream _managedResource = new System.IO.MemoryStream(); 
     public string ThreadName { get; set; } 

     public void Dispose() { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     ~DisposableClass() { 
      Console.WriteLine("Disposing object created on thread " + this.ThreadName); 
      Dispose(false); 
     } 

     private void Dispose(bool disposing) { 
      if (disposing) { 
       // free managed resources 
       if (_managedResource != null) { 
        _managedResource.Dispose(); 
        _managedResource = null; 
       } 
      } 
      // free native resources if there are any. 
      if (_nativeResource != IntPtr.Zero) { 
       Marshal.FreeHGlobal(_nativeResource); 
       _nativeResource = IntPtr.Zero; 
      } 
     } 
    } 

    static class Program { 
     private static Lazy<DisposableClass> _lazy; 

     [STAThread] 
     static void Main() { 
      List<Thread> t1 = new List<Thread>(); 

      for (int u = 2, i = 0; i <= u; i++) 
       t1.Add(new Thread(new ThreadStart(InitializeLazyClass)) { Name = i.ToString() }); 
      t1.ForEach(t => t.Start()); 
      t1.ForEach(t => t.Join()); 

      Console.WriteLine("The winning thread was " + _lazy.Value.ThreadName); 
      Console.WriteLine("Garbage collecting..."); 
      GC.Collect(); 
      Thread.Sleep(2000); 
      Console.WriteLine("Application exiting..."); 
     } 

     static void InitializeLazyClass() { 
      _lazy = new Lazy<DisposableClass>(LazyThreadSafetyMode.PublicationOnly); 
      _lazy.Value.ThreadName = Thread.CurrentThread.Name; 
     } 
    } 
} 

使用LazyThreadSafetyMode.PublicationOnly,它创建三个线程,每个实例Lazy<DisposableClass>然后退出。

输出看起来是这样的:

获胜的线程1

垃圾回收......

处置上线2

处置对象创建的对象上创建线程0

正在退出...

处置对象上线程1

创建正如在问题中提到,Lazy<>.LazyInitValue()不检查IDisposable的,并Dispose()没有被显式调用,但仍所有三个对象最终被设置;两个物体由于超出范围而被丢弃并被垃圾收集,而第三个被丢弃在申请出口处。发生这种情况是因为我们正在使用在所有托管对象被销毁时调用的析构函数/终结器,并反过来使用它来确保释放非托管资源。

对于第二个问题,人们猜测为什么没有使用IDisposable检查。也许他们没有设想在实例化时分配非托管资源。

延伸阅读:

更多关于如何正确实现IDisposable,看MSDN上here(这是我的例子中的一半是从来源)。

此外,还有是一个很好的SO文章here,这给了我见过的为什么的IDisposable应该以这种方式实现了最好的解释。