使用Lazy<T>,并设置LazyThreadSafeMode.PublicationOnly和IDisposable。

8
今天我在使用Lazy<T>时发现了一个有趣的情况(在我看来)。 http://msdn.microsoft.com/zh-cn/library/system.threading.lazythreadsafetymode.aspx
  • PublicationOnly:

    When multiple threads try to initialize a Lazy instance simultaneously, all threads are allowed to run the initialization method ... Any instances of T that were created by the competing threads are discarded.

    If we look at the code of Lazy<T>.LazyInitValue() we will find that there is no check for IDisposable implementation and resoruces may leak here:

     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

所以我有两个问题:

  • Do I miss something or we can see that few insntance can be created and resources can leak in this situation ?
  • If it's correct assumption why not to check for IDisposable in this situation and implement Dispose() on Boxed<T> such that it delegates disposal to the Boxed instance of T if it implements IDisposable or in some different way:

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

1
这在.NET上被忽视了,这就是为什么我提供LayzNeedle<T>LazyDisposableNeedle<T>的原因。顺便说一句,我的Lazy<T>的后移植模仿了这种行为(看看代码末尾)。 - Theraot
1个回答

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;
        }
    }
}

使用LazyThreadSafetyMode.PublicationOnly,它创建了三个线程,每个线程都实例化了Lazy<DisposableClass>然后退出。
输出看起来像这样:
引用块:

获胜线程是1

垃圾回收...

在第2个线程上创建的对象正在处置中

在第0个线程上创建的对象正在处置中

应用程序正在退出...

在第1个线程上创建的对象正在处置中

如问题中所述,Lazy<>.LazyInitValue()不会检查IDisposable,也没有显式调用Dispose(),但仍然最终处理了所有三个对象; 两个对象被处理,因为它们超出了范围并被垃圾收集,第三个对象在应用程序退出时被处理。这是因为我们利用了析构函数/终结器,当所有托管对象被销毁时调用它,并利用它确保我们的非托管资源被释放。
对于第二个问题,任何人都可以猜测为什么没有放置IDisposable检查。也许他们没想到在实例化时分配非托管资源。
进一步阅读:
要了解如何正确实现IDisposable,请查看MSDN此处(其中的一半示例来源)。
此外,这里有一篇出色的SO文章此处,它提供了我见过的最好的IDisposable实现方式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接