Подтвердить что ты не робот

Каков правильный способ удаления элементов, находящихся внутри 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!");
    }
}
4b9b3361

Ответ 1

Я просмотрел код 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();

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

Поэтому я не позволю прямому назначению одноразового значения для ThreadLocalDisposables и разрешу только конструктор factory.

Здесь 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; }
    }
}

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

Ответ 2

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

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

Ответ 3

В .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();
    }
}

Ответ 4

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

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

Ответ 5

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

Ответ 6

Ссылка MSDN указывает, что значения ThreadLocal должны быть удалены потоком, используя их после его завершения. Однако в некоторых случаях, таких как потоки событий с использованием потока потоков A потока, можно использовать значение и уходить, чтобы сделать что-то еще, а затем вернуться к значению 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);
    }
}