Как правильно расположить элементы, хранящиеся внутри ThreadLocal<IDisposable>?

Когда вы используете ThreadLocal<T> и T реализует IDisposable, как вы должны избавиться от членов, находящихся внутри ThreadLocal?

В соответствии с ILSpy методы Dispose() и Dispose(bool) ThreadLocal

public void Dispose()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
    int currentInstanceIndex = this.m_currentInstanceIndex;
    if (currentInstanceIndex > -1 && Interlocked.CompareExchange(ref this.m_currentInstanceIndex, -1, currentInstanceIndex) == currentInstanceIndex)
    {
        ThreadLocal<T>.s_availableIndices.Push(currentInstanceIndex);
    }
    this.m_holder = null;
}

Не похоже, что ThreadLocal пытается вызвать Dispose для своих дочерних элементов. Я не могу сказать, как ссылаться на каждый поток, который он выделил внутри, чтобы я мог позаботиться об этом.


Я выполнил тест со следующим кодом, класс никогда не утилизируется

static class Sandbox
{
    static void Main()
    {

        ThreadLocal<TestClass> test = new ThreadLocal<TestClass>();
        test.Value = new TestClass();

        test.Dispose();
        Console.Read();
    }
}

class TestClass : IDisposable
{
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    protected void Dispose(bool Disposing)
    {
        Console.Write("I was disposed!");
    }
}

6 ответов

Решение

Я посмотрел на код в ThreadLocal<T> чтобы увидеть, что в настоящее время Dispose делает, и это, кажется, много вуду. Очевидно, избавиться от вещей, связанных с потоками.

Но он не располагает ценностями, если T Сам по себе одноразовый.

Теперь у меня есть решение - ThreadLocalDisposables<T> класс, но прежде чем дать полное определение, стоит подумать о том, что должно произойти, если вы написали этот код:

var tl = new ThreadLocalDisposables<IExpensiveDisposableResource>();
tl.Value = myEdr1;
tl.Value = myEdr2;
tl.Dispose();

Если оба myEdr1 & myEdr2 как быть утилизированным? Или просто myEdr2? Или должен myEdr1 быть расположенным когда myEdr2 был назначен?

Мне не ясно, какой должна быть семантика.

Однако для меня ясно, что если бы я написал этот код:

var tl = new ThreadLocalDisposables<IExpensiveDisposableResource>(
    () => new ExpensiveDisposableResource());
tl.Value.DoSomething();
tl.Dispose();

Тогда я ожидал бы, что ресурс, созданный фабрикой для каждого потока, должен быть удален.

Так что я не собираюсь позволять прямое присвоение одноразового значения для ThreadLocalDisposables и разрешить только фабричный конструктор.

Вот ThreadLocalDisposables:

public class ThreadLocalDisposables<T> : IDisposable
    where T : IDisposable
{
    private ThreadLocal<T> _threadLocal = null;
    private ConcurrentBag<T> _values = new ConcurrentBag<T>();

    public ThreadLocalDisposables(Func<T> valueFactory)
    {
        _threadLocal = new ThreadLocal<T>(() =>
        {
            var value = valueFactory();
            _values.Add(value);
            return value;
        });
    }

    public void Dispose()
    {
        _threadLocal.Dispose();
        Array.ForEach(_values.ToArray(), t => t.Dispose());
    }

    public override string ToString()
    {
        return _threadLocal.ToString();
    }

    public bool IsValueCreated
    {
        get { return _threadLocal.IsValueCreated; }
    }

    public T Value
    {
        get { return _threadLocal.Value; }
    }
}

Это помогает?

В.NET 4.5 свойство Threadues было добавлено в ThreadLocal<> для решения проблемы ручного управления временем жизни объектов TheadLocal. Он возвращает список всех текущих экземпляров, связанных с этой переменной ThreadLocal.

В этой статье MSDN был представлен пример использования цикла Parallel.For для доступа к пулу соединений с базой данных ThreadLocal. Соответствующий фрагмент кода ниже.

var threadDbConn = new ThreadLocal<MyDbConnection>(() => MyDbConnection.Open(), true);
try
{
    Parallel.For(0, 10000, i =>
    {
        var inputData = threadDbConn.Value.GetData(i);
        ...
    });
}
finally
{
    foreach(var dbConn in threadDbConn.Values)
    {
        dbConn.Close();
    }
}

Обычно, когда вы явно не избавляетесь от класса, который содержит неуправляемый ресурс, сборщик мусора в конечном итоге запустится и утилизирует его. Для этого у класса должен быть финализатор, который избавляется от своего ресурса. В вашем учебном классе нет финализатора.

Теперь, чтобы избавиться от класса, который проводится внутри ThreadLocal<T> где Т есть IDisposable Вы также должны сделать это самостоятельно. ThreadLocal<T> это просто обертка, она не будет пытаться угадать, каково правильное поведение для ее свернутой ссылки, когда она сама удаляется. Класс может, например, пережить свое локальное хранилище потока.

Как вызывается сам метод ThreadLocal.Dispose? Я ожидаю, что это, скорее всего, будет внутри чего-то вроде блока "использования". Я бы предложил обернуть блок "using" для ThreadLocal блоком "using" для ресурса, который будет там храниться.

Это связано с ThreadLocal<> и утечкой памяти

Я думаю, потому что нет IDisposable ограничение на Tпредполагается, что пользователь ThreadLocal<T> при необходимости избавится от местного объекта.

Ссылка MSDN гласит, что значения ThreadLocal должны быть удалены потоком, используя их, как только это будет сделано. Однако в некоторых случаях, например, в потоке событий с использованием пула потоков. Поток может использовать это значение и уйти, чтобы сделать что-то еще, а затем вернуться к значению N несколько раз.

Конкретный пример - это то, где я хочу, чтобы Entity Framework DBContext сохранялся на протяжении всей жизни ряда рабочих потоков служебной шины.

Я написал следующий класс, который я использую в этих случаях: либо DisposeThreadCompletedValues ​​может вызываться вручную другим потоком, либо может быть активирован внутренний поток монитора.

Надеюсь, это поможет?

using System.Threading;
public class DisposableThreadLocal<T> : IDisposable
    where T : IDisposable
{
    public DisposableThreadLocal(Func<T> _ValueFactory)
    {
        Initialize(_ValueFactory, false, 1);
    }
    public DisposableThreadLocal(Func<T> _ValueFactory, bool CreateLocalWatcherThread, int _CheckEverySeconds)
    {
        Initialize(_ValueFactory, CreateLocalWatcherThread, _CheckEverySeconds);
    }

    private void Initialize(Func<T> _ValueFactory, bool CreateLocalWatcherThread, int _CheckEverySeconds)
    {
        m_ValueFactory = _ValueFactory;
        m_CheckEverySeconds = _CheckEverySeconds * 1000;
        if (CreateLocalWatcherThread)
        {
            System.Threading.ThreadStart WatcherThreadStart;
            WatcherThreadStart = new ThreadStart(InternalMonitor);
            WatcherThread = new Thread(WatcherThreadStart);
            WatcherThread.Start();
        }
    }

    private object SyncRoot = new object();

    private Func<T> m_ValueFactory;
    public Func<T> ValueFactory
    {
        get
        {
            return m_ValueFactory;
        }
    }

    private Dictionary<Thread, T> m_InternalDict = new Dictionary<Thread, T>();
    private Dictionary<Thread, T> InternalDict
    {
        get
        {
            return m_InternalDict;
        }
    }

    public T Value
    {
        get
        {
            T Result;
            lock(SyncRoot)
            {
                if (!InternalDict.TryGetValue(Thread.CurrentThread,out Result))
                {
                    Result = ValueFactory.Invoke();
                    InternalDict.Add(Thread.CurrentThread, Result);
                }
            }
            return Result;
        }
        set
        {
            lock (SyncRoot)
            {
                if (InternalDict.ContainsKey(Thread.CurrentThread))
                {
                    InternalDict[Thread.CurrentThread] = value;
                }
                else
                {
                    InternalDict.Add(Thread.CurrentThread, value);
                }
            }
        }
    }

    public bool IsValueCreated
    {
        get
        {
            lock (SyncRoot)
            {
                return InternalDict.ContainsKey(Thread.CurrentThread);
            }
        }
    }

    public void DisposeThreadCompletedValues()
    {
        lock (SyncRoot)
        {
            List<Thread> CompletedThreads;
            CompletedThreads = new List<Thread>();
            foreach (Thread ThreadInstance in InternalDict.Keys)
            {
                if (!ThreadInstance.IsAlive)
                {
                    CompletedThreads.Add(ThreadInstance);
                }
            }
            foreach (Thread ThreadInstance in CompletedThreads)
            {
                InternalDict[ThreadInstance].Dispose();
                InternalDict.Remove(ThreadInstance);
            }
        }
    }

    private int m_CheckEverySeconds;
    private int CheckEverySeconds
    {
        get
        {
            return m_CheckEverySeconds;
        }
    }

    private Thread WatcherThread;

    private void InternalMonitor()
    {
        while (!IsDisposed)
        {
            System.Threading.Thread.Sleep(CheckEverySeconds);
            DisposeThreadCompletedValues();
        }
    }

    private bool IsDisposed = false;
    public void Dispose()
    {
        if (!IsDisposed)
        {
            IsDisposed = true;
            DoDispose();
        }
    }
    private void DoDispose()
    {
        if (WatcherThread != null)
        {
            WatcherThread.Abort();
        }
        //InternalDict.Values.ToList().ForEach(Value => Value.Dispose());
        foreach (T Value in InternalDict.Values)
        {
            Value.Dispose();
        }
        InternalDict.Clear();
        m_InternalDict = null;
        m_ValueFactory = null;
        GC.SuppressFinalize(this);
    }
}
Другие вопросы по тегам