处理ThreadLocal <idisposable>中的元素的正确方法是什么?</idisposable>

时间:2011-10-06 02:04:42

标签: c# .net-4.0 idisposable thread-local

当你使用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!");
    }
}

6 个答案:

答案 0 :(得分:12)

我查看了ThreadLocal<T>中的代码,了解当前Dispose正在做什么,看起来似乎是很多伏都教。显然处理与线程相关的东西。

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

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

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

myEdr1&amp; 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; }
    }
}

这有帮助吗?

答案 1 :(得分:4)

在.NET 4.5中,Values属性已添加到ThreadLocal&lt;&gt;处理手动管理TheadLocal对象的生命周期的问题。它返回绑定到该ThreadLocal变量的所有当前实例的列表。

使用Parallel.For循环访问ThreadLocal数据库连接池的示例在this MSDN article中提供。相关的代码段如下所示。

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 :(得分:2)

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

现在,要处理一个位于ThreadLocal<T>里面的类,其中T是IDisposable,你也必须自己做。 ThreadLocal<T>只是一个包装器,当它自身被处理时,它不会试图猜测它的包装引用的正确行为。例如,该类可以在其线程本地存储中存活。

答案 3 :(得分:1)

这与ThreadLocal<> and memory leak

有关

我的猜测是因为IDisposable上没有T约束,假设ThreadLocal<T>的用户在适当的时候会处置本地对象。

答案 4 :(得分:1)

如何调用ThreadLocal.Dispose方法本身?我希望它很可能会像“使用”块一样。我建议用ThreadLocal的“using”块包装一个“using”块,用于存储在那里的资源。

答案 5 :(得分: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);
    }
}