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