使用LazyThreadSafeMode.PublicationOnly和IDisposable延迟<T>

And*_*nov 7 idisposable c#-4.0 lazythreadsafetymode

今天我和Lazy一起玩,<T>发现了一个有趣的案例(在我看来).

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

  • PublicationOnly:

    当多个线程尝试同时初始化Lazy实例时,允许所有线程运行初始化方法...将丢弃由竞争线程创建的任何T实例.

    如果我们查看Lazy <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;
    
    Run Code Online (Sandbox Code Playgroud)

截至目前,确保仅创建实例的唯一方法是使用LazyThreadSafetyMode.ExceptionAndPublication.

所以我有两个问题:

  • 我是否会遗漏某些内容,或者我们可以看到在这种情况下可以创建很少的漏洞并且资源可能会泄漏?
  • 如果正确的假设为什么不在这种情况下检查IDisposable并在Boxed上实现Dispose(),<T>以便它将处理委托给Boxed实例,T如果它实现IDisposable或以某种不同的方式:

       class Boxed<T>
       {
            internal T m_value;
            void Dispose()
            {
                if (m_value is IDisposable)
                {     ((IDisposable) m_value).Dispose();  }
            }
       }
    
    Run Code Online (Sandbox Code Playgroud)

cok*_*n19 3

要回答第一个问题,如果一个类“正确”实现了 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

输出看起来像这样:

获胜线程为 1

垃圾收集...

处理在线程 2 上创建的对象

处理在线程 0 上创建的对象

应用程序正在退出...

处理在线程 1 上创建的对象

正如问题中提到的,Lazy<>.LazyInitValue()不检查 IDisposable,并且Dispose()没有被显式调用,但最终仍然处置了所有三个对象;两个对象因超出范围并被垃圾收集而被处置,第三个对象在应用程序退出时被处置。发生这种情况是因为我们正在使用销毁所有托管对象时调用的析构函数/终结器,进而使用它来确保释放我们的非托管资源。

对于第二个问题,任何人都可以猜测为什么没有进行 IDisposable 检查。也许他们没有想到在实例化时分配非托管资源。

进一步阅读:

有关如何正确实现 IDisposable 的更多信息,请查看此处的MSDN (这是我的示例的一半来源)。

另外,这里有一篇很棒的 SO 文章,它给出了我所见过的关于为什么IDisposable 应该以这种方式实现的最好解释。