Ленивый <T>с LazyThreadSafeMode.PublicationOnly и IDisposable - PullRequest
7 голосов
/ 15 мая 2011

Сегодня я играл с Ленивым <T> и нашел интересный случай (на мой взгляд).

http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx

  • PublicationOnly:

    Когда несколько потоков пытаются инициализировать экземпляр 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.

Итак, у меня есть 2 вопроса:

  • Я что-то пропустил или мыМожно ли увидеть, что в этой ситуации может быть создано небольшое количество страховки, а ресурсы могут просочиться?
  • Если это правильное предположение, почему бы не проверить наличие IDisposable в этой ситуации и реализовать Dispose () в штучной упаковке <T>, чтобыон передает распоряжение экземпляру T в штучной упаковке, если он реализует IDisposable или каким-либо другим способом:

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

1 Ответ

2 голосов
/ 08 марта 2014

Чтобы ответить на первый вопрос, если класс реализует 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 должен быть реализован таким образом.

...