Lazy <T> with LazyThreadSafeMode.PublicationOnly and IDisposable
Today I played with Lazy <T>
and found an interesting case (in my opinion).
http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx
Publication only:
When several threads try to initialize a Lazy instance at the same time, all threads are allowed to start the initialization method ... Any T instances created by competing threads are discarded.
If we look at the Lazy
<T>
.LazyInitValue () code, we find that there is no check for the implementation of IDisposable, and resorption can occur 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;
Currently, the only way to ensure that only an instance is created is to use LazyThreadSafetyMode.ExceptionAndPublication
.
I have 2 questions:
- Am I missing something, or can we see that a small amount of insntance can be created and resources can leak in this situation?
If you correct the assumption, why not check IDisposable in this situation and implement Dispose () in Boxed
<T>
so that it delegates the removal to the boxed instanceT
, 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 "correctly", then no, there should not be a risk of a resource leak. However, there may be a delay in which unmanaged resources remain idle 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 Lazy<DisposableClass>
instance then exits.
The result looks something like this:
The winning thread was 1
Garbage Collection ...
Disposal of an object created in thread 2
Disposal of an object created in stream 0
Application exit ...
Disposal of an object created in stream 1
As mentioned in the question, Lazy<>.LazyInitValue()
does not check for IDisposable, and Dispose()
not called explicitly, but still all three objects were ultimately deleted; two objects were deleted due to the fact that they fell out of scope and garbage collected, and the third was placed at the exit of the application. This is because we use a destructor / finalizer that is called when all managed objects are destroyed and, in turn, use it to ensure the release of our unmanaged resources.
In the second question, does anyone know why the identification check was not installed. Perhaps they did not assume that unmanaged resources were allocated when the instance was created.
Further reading:
For more details on how to correctly implement IDisposable, see the MSDN here (where half of my example is from the source).
Also, there is a great SO article here that gives the best explanation I've ever seen why IDisposable should be implemented this way.