Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dispose of ThreadLocal value when thread exits?

Let's say I have a disposable type:

class MyDisposable : IDisposable
{
    public void Dispose() { }
}

Then I want every thread to have its own local copy of this class, one per thread:

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

Now I want to make sure that right before the thread exits, I call Dispose() on the value, if it was created. Is there any way to do that? Some threads in my application are short-lived and some are long-lived. I want to make sure any short-lived ones dispose of their values.

like image 697
Scott Whitlock Avatar asked Jun 26 '14 14:06

Scott Whitlock


People also ask

How to Dispose threads in c#?

Abort(Object) Generally, this method is used to terminate the thread. Syntax: public void Abort(object information); Here, the information contains any information that you want to pass in a thread when it is being stopped.

What is ThreadLocal in java?

ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.


2 Answers

There is no hook in the CLR to be notified about a thread exit. The thread-local value will be deleted eventually by the GC but you seem to be interested in quick disposal.

The only way is to make the thread perform the cleanup itself.

If you use tasks instead of threads you can attach a continuation to that task and make sure the resource is cleaned up and reset after the task completes. You can't use a ThreadLocal here, though, because continuations are never guaranteed to run on the same thread.

like image 132
usr Avatar answered Sep 20 '22 04:09

usr


I ended up implementing my own class called ThreadLocalDisposable<T>. What it does is start a "cleanup" thread when created, and keep track of all the objects it has created, per thread, in a ConcurrentDictionary. Once a thread has stopped, it will eventually call the Action<T> cleanupMethod on it, which can simply be x => x.Dispose() if you like.

Just beware that the cleanup code is called on the cleanup thread, not on the thread that it was actually created and used on, so whatever you do there has to be thread-safe.

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
        }
    }
}

Known issues: I meant for this class to live for the lifetime of the application, so when you dispose of it, it just does its best to clean-up, but it's not certain to clean-up the whole list of objects, because other threads could still call the Value getter and create new objects after the disposing thread got its last enumerable copy.

like image 26
Scott Whitlock Avatar answered Sep 22 '22 04:09

Scott Whitlock