2011-05-15 86 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應該以這種方式實現了最好的解釋。