Today I was playing with Lazy<T>
and found an interesting case (to my mind).
http://msdn.microsoft.com/en-us/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. 开发者_C百科boxed = (Boxed<T>) this.m_boxed; } break;
As of now the only way to ensure that only instance is created is to use LazyThreadSafetyMode.ExceptionAndPublication
.
So I have 2 questions:
- 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 ofT
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(); } } }
To answer the first question, if a class implements IDisposable "properly" then no, there should be no danger of a resource leak. There may however be a delay in which unmanaged resources would remain un-released until garbage collection occurs.
Consider the following application:
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;
}
}
}
Using LazyThreadSafetyMode.PublicationOnly
, it creates three threads, each instantiating Lazy<DisposableClass>
then exits.
The output looks something like this:
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
As mentioned in the question, Lazy<>.LazyInitValue()
does not check for IDisposable, and Dispose()
is not being called explicitly, yet still all three objects were ultimately disposed; two objects were disposed because they fell out of scope and were garbage collected, and the third was disposed on application exit. This occurs because we're making use of the destructor/finalizer that is called when all managed objects are destroyed, and in turn, using it to ensure that our unmanaged resources are released.
To the second question, it's anyone's guess as to why no IDisposable check was put in place. Perhaps they didn't envision unmanaged resources being allocated upon instantiation.
Further reading:
For more on how to properly implement IDisposable, look on MSDN here (which is where half of my example is sourced from).
Also, there is an excellent SO article here, which gives the best explanation I've ever seen of why IDisposable should be implemented this way.
精彩评论