Как правильно расположить элементы, хранящиеся внутри ThreadLocal <IDisposable>? - PullRequest
25 голосов
/ 06 октября 2011

Когда вы используете 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 ]

12 голосов
/ 06 октября 2011

Я взглянул на код в 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; }
    }
}

Помогает ли это?

4 голосов
/ 04 июня 2014

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

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

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();
    }
}
2 голосов
/ 06 октября 2011

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

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

1 голос
/ 26 октября 2016

Ссылка 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);
    }
}
1 голос
/ 06 октября 2011

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

1 голос
/ 06 октября 2011

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

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

...