当线程退出时如何处理ThreadLocal的值?

8
假设我有一个一次性类型:
class MyDisposable : IDisposable
{
    public void Dispose() { }
}

我希望每个线程都拥有自己的本地类副本,每个线程一个:

private readonly ThreadLocal<MyDisposable> localMyDisposable
    = new ThreadLocal<MyDisposable>(() => new MyDisposable());

现在我想确保在线程退出之前,如果已创建值,则调用Dispose()。有没有办法做到这一点?我的应用程序中的一些线程是短暂的,另一些则是长期存在的。我想确保任何短暂存在的线程都可以处理它们的值。

2
因此,在线程过程退出之前,请调用“Dispose”。只需将您的线程过程包装在“try ... finally”中即可。 - Jim Mischel
@JimMischel - 请假设这是我正在编写的一个库,我不想依赖第三方调用代码来进行清理(如果可能的话)。 - Scott Whitlock
Jim的评论仍然是正确的。该线程实际上有某种“主函数”。你为什么不能在最后清理它? - James Barrass
@JamesBarrass - 我无法控制线程的创建。库用户可以以任何方式创建线程,如果他们调用我的库函数(任意次数),那么我的代码需要看到我正在创建的每个对象的特定于线程的实例。我不知道他们的线程何时退出,但我希望得到通知,以便我可以进行清理。当然这并不难以相信。 - Scott Whitlock
2个回答

6

CLR中没有钩子可以通知线程退出,线程本地值最终将被GC删除,但您似乎对快速处理感兴趣。

唯一的方法是使线程自行执行清理。

如果您使用任务而不是线程,则可以将连续附加到该任务并确保资源在任务完成后得到清理和重置。但是,您不能在此处使用ThreadLocal,因为不能保证连续运行在同一线程上。


这里唯一重要的一点是,CLR中没有钩子可以通知线程退出。那么这与这个问题有什么关系?(https://dev59.com/F2kv5IYBdhLWcg3wqiqS) - Scott Whitlock

2
我最终实现了自己的类,名为ThreadLocalDisposable<T>。它创建时会启动一个“清理”线程,并使用ConcurrentDictionary跟踪每个线程中创建的所有对象。一旦线程停止,它最终将调用Action<T> cleanupMethod来进行清理,可以简单地写成x => x.Dispose()

请注意,清理代码是在清理线程上调用的,而不是在实际创建和使用该代码的线程上,因此您在此处执行的任何操作都必须是线程安全的。

public class ThreadLocalDisposable<T> : IDisposable
    where T : IDisposable
{
    private readonly ConcurrentDictionary<Thread, T> valuesByThread
        = new ConcurrentDictionary<Thread, T>();
    private readonly Func<T> factoryMethod;
    private object disposedLock = new object();
    private bool disposed = false;

    public ThreadLocalDisposable(
        Func<T> factoryMethod,
        Action<T> cleanupMethod)
    {
        if (factoryMethod == null) throw new ArgumentNullException("factoryMethod");
        if (cleanupMethod == null) throw new ArgumentNullException("cleanupMethod");
        this.factoryMethod = factoryMethod;

        var disposerThread = new Thread(() =>
        {
            bool exit = false;
            while (!exit)
            {
                Thread.Sleep(1000);
                var removedValuesByThread = new List<KeyValuePair<Thread, T>>();
                foreach (var valueByThread in this.valuesByThread)
                {
                    if ((valueByThread.Key.ThreadState & ThreadState.Stopped) == ThreadState.Stopped)
                    {
                        removedValuesByThread.Add(valueByThread);
                    }
                }
                foreach (var removedValueByThread in removedValuesByThread)
                {
                    T value;
                    while (!this.valuesByThread.TryRemove(removedValueByThread.Key, out value))
                    {
                        Thread.Sleep(2);
                    }
                    cleanupMethod(value);
                }
                lock (this.disposedLock)
                {
                    exit = this.disposed;
                }
            }

            foreach (var valueByThread in this.valuesByThread)
            {
                var value = valueByThread.Value;
                cleanupMethod(value);
            }
            this.valuesByThread.Clear();
        });
        disposerThread.Start();
    }

    public T Value
    {
        get
        {
            if (!this.valuesByThread.ContainsKey(Thread.CurrentThread))
            {
                var newValue = this.factoryMethod();
                while (!this.valuesByThread.TryAdd(Thread.CurrentThread, newValue))
                {
                    Thread.Sleep(2);
                }
            }
            var result = this.valuesByThread[Thread.CurrentThread];
            return result;
        }
    }

    public void Dispose()
    {
        lock (this.disposedLock)
        {
            this.disposed = true; // tells disposer thread to shut down
        }
    }
}

已知问题:我希望这个类能够在应用程序的整个生命周期中存在,所以当您处理它时,它会尽力清理,但不能确定是否清理了整个对象列表,因为其他线程仍可能调用 Value getter 并在处理线程获取其最后一个可枚举副本后创建新对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接