今天我玩懒惰<T>
,发现了一个有趣的案例(在我看来)。
http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx
-
仅出版物:
当多个线程尝试同时初始化一个 Lazy 实例时,允许所有线程运行初始化方法......由竞争线程创建的任何 T 实例都将被丢弃。
如果我们看一下代码 懒惰
<T>
.LazyInitValue() 我们会发现没有检查 IDisposable 实现,并且资源可能会泄漏到这里: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
.
所以我有两个问题:
- 我是否错过了什么,或者我们可以看到在这种情况下可以创建很少的插入并且资源可能会泄漏?
-
如果假设正确,为什么不在这种情况下检查 IDisposable 并在盒装
<T>
上实现 Dispose(),以便在实现 IDisposable 或以某种不同的方式将处置委托给T
的 Boxed 实例:class Boxed<T> { internal T m_value; void Dispose() { if (m_value is IDisposable) { ((IDisposable) m_value).Dispose(); } } }
要回答第一个问题,如果一个类"正确"地实现了 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应该以这种方式实现。