28

当您使用ThreadLocal<T>T实现 IDisposable 时,您应该如何处理 ThreadLocal 中保存的成员?

根据 ILSpy,ThreadLocal 的 Dispose() 和 Dispose(bool) 方法是

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!");
    }
}
4

6 回答 6

14

我查看了代码ThreadLocal<T>以了解当前Dispose正在做什么,这似乎是很多巫术。显然是在处理线程相关的东西。

T但如果它本身是一次性的,它不会处理这些值。

现在,我有一个解决方案 - 一个ThreadLocalDisposables<T>类,但在给出完整定义之前,值得考虑一下如果你编写了这段代码会发生什么:

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

两者myEdr1myEdr2应该被处置吗?还是只是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; }
    }
}

这有帮助吗?

于 2011-10-06T06:05:03.953 回答
7

在 .NET 4.5 中,ThreadLocal<> 中增加了Values属性来处理手动管理 ThreadLocal 对象生命周期的问题。它返回绑定到该 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();
    }
}
于 2014-06-03T20:17:58.803 回答
2

通常,当您没有显式处置包含非托管资源的类时,垃圾收集器最终会运行并处置它。为此,该类必须有一个终结器来处理其资源。您的示例类没有终结器。

ThreadLocal<T>现在,要处理在where T is 中举行的课程,IDisposable您还必须自己做。ThreadLocal<T>只是一个包装器,它不会尝试猜测其包装的引用在其自身被处置时的正确行为是什么。例如,该类可以在其线程本地存储中幸存下来。

于 2011-10-06T02:24:06.607 回答
1

这与ThreadLocal<> 和内存泄漏有关

我的猜测是因为没有IDisposable限制T,所以假设用户ThreadLocal<T>将在适当的时候处理本地对象。

于 2011-10-06T02:41:28.137 回答
1

ThreadLocal.Dispose 方法本身是如何被调用的?我希望它很可能在“使用”块之类的东西中。我建议将 ThreadLocal 的“使用”块与将存储在那里的资源的“使用”块包装起来。

于 2011-10-06T19:58:54.183 回答
1

MSDN 参考声明 ThreadLocal 值应该由使用它们的线程在完成后处理。然而,在某些情况下,例如使用线程池的事件线程,一个线程可能会使用该值并开始执行其他操作,然后 N 次返回该值。

具体示例是我希望实体框架 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);
    }
}
于 2016-10-26T02:11:05.687 回答