今天我在玩Lazy <T>
并发现了一个有趣的案例(在我看来)。
http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx
仅发布:
当多个线程尝试同时初始化 Lazy 实例时,所有线程都可以运行初始化方法...由竞争线程创建的任何 T 实例都将被丢弃。
如果我们看一下Lazy
<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 并在 Boxed
<T>
上实现 Dispose()这样它将处理委托(delegate)给T
的 Boxed 实例如果它实现 IDisposable 或以某种不同的方式: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>
然后退出。
输出看起来像这样:
The winning thread was 1
Garbage collecting...
Disposing object created on thread 2
Disposing object created on thread 0
Application exiting...
Disposing object created on thread 1
正如问题中提到的,Lazy<>.LazyInitValue()
不检查 IDisposable,并且 Dispose()
没有被显式调用,但最终仍然处置了所有三个对象;两个对象因超出范围并被垃圾收集而被处置,第三个对象在应用程序退出时被处置。发生这种情况是因为我们正在使用销毁所有托管对象时调用的析构函数/终结器,进而使用它来确保释放我们的非托管资源。
对于第二个问题,任何人都可以猜测为什么没有进行 IDisposable 检查。也许他们没有想到在实例化时分配非托管资源。
进一步阅读:
有关如何正确实现 IDisposable 的更多信息,请参阅 MSDN here (这是我的示例的一半来源)。
此外,还有一篇优秀的 SO 文章 here ,它给出了我所见过的关于为什么 IDisposable 应该以这种方式实现的最好解释。
关于c#-4.0 - Lazy<T> 与 LazyThreadSafeMode.PublicationOnly 和 IDisposable,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6007996/