Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do I Detect When a Client Thread Exits?

Here’s an interesting library writer’s dilemma. In my library (in my case EasyNetQ) I’m assigning thread local resources. So when a client creates a new thread and then calls certain methods on my library new resources get created. In the case of EasyNetQ a new channel to the RabbitMQ server is created when the client calls ‘Publish’ on a new thread. I want to be able to detect when the client thread exits so that I can clean up the resources (channels).

The only way of doing this I’ve come up with is to create a new ‘watcher’ thread that simply blocks on a Join call to the client thread. Here a simple demonstration:

First my ‘library’. It grabs the client thread and then creates a new thread which blocks on ‘Join’:

public class Library
{
    public void StartSomething()
    {
        Console.WriteLine("Library says: StartSomething called");

        var clientThread = Thread.CurrentThread;
        var exitMonitorThread = new Thread(() =>
        {
            clientThread.Join();
            Console.WriteLine("Libaray says: Client thread existed");
        });

        exitMonitorThread.Start();
    }
}

Here’s a client that uses my library. It creates a new thread and then calls my library’s StartSomething method:

public class Client
{
    private readonly Library library;

    public Client(Library library)
    {
        this.library = library;
    }

    public void DoWorkInAThread()
    {
        var thread = new Thread(() =>
        {
            library.StartSomething();
            Thread.Sleep(10);
            Console.WriteLine("Client thread says: I'm done");
        });
        thread.Start();
    }
}

When I run the client like this:

var client = new Client(new Library());

client.DoWorkInAThread();

// give the client thread time to complete
Thread.Sleep(100);

I get this output:

Library says: StartSomething called
Client thread says: I'm done
Libaray says: Client thread existed

So it works, but it's ugly. I really don’t like the idea of all these blocked watcher threads hanging around. Is there a better way of doing this?

First alternative.

Provide a method that returns a worker that implements IDisposable and make it clear in the documentation that you should not share workers between threads. Here's the modified library:

public class Library
{
    public LibraryWorker GetLibraryWorker()
    {
        return new LibraryWorker();
    }
}

public class LibraryWorker : IDisposable
{
    public void StartSomething()
    {
        Console.WriteLine("Library says: StartSomething called");
    }

    public void Dispose()
    {
        Console.WriteLine("Library says: I can clean up");
    }
}

The client is now a little more complicated:

public class Client
{
    private readonly Library library;

    public Client(Library library)
    {
        this.library = library;
    }

    public void DoWorkInAThread()
    {
        var thread = new Thread(() =>
        {
            using(var worker = library.GetLibraryWorker())
            {
                worker.StartSomething();
                Console.WriteLine("Client thread says: I'm done");
            }
        });
        thread.Start();
    }
}

The main problem with this change is that it's a breaking change for the API. Existing clients will have to be re-written. Now that's not such a bad thing, it would mean revisiting them and making sure they are cleaning up correctly.

Non-breaking second alternative. The API provides a way for the client to declare 'work scope'. Once the scope completes, the library can clean up. The library provides a WorkScope that implements IDisposable, but unlike the first alternative above, the StartSomething method stays on the Library class:

public class Library
{
    public WorkScope GetWorkScope()
    {
        return new WorkScope();
    }

    public void StartSomething()
    {
        Console.WriteLine("Library says: StartSomething called");
    }
}

public class WorkScope : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Library says: I can clean up");
    }
}

The client simply puts the StartSomething call in a WorkScope...

public class Client
{
    private readonly Library library;

    public Client(Library library)
    {
        this.library = library;
    }

    public void DoWorkInAThread()
    {
        var thread = new Thread(() =>
        {
            using(library.GetWorkScope())
            {
                library.StartSomething();
                Console.WriteLine("Client thread says: I'm done");
            }
        });
        thread.Start();
    }
}

I like this less than the first alternative because it doesn't force the library user to think about scope.

like image 517
Mike Hadlow Avatar asked May 03 '12 13:05

Mike Hadlow


People also ask

What happens to threads when process exits?

Threads are part of the process. If the process exits, they (along with all other process resources) cease to exist.

Do threads terminate automatically?

A thread automatically terminates when it returns from its entry-point routine. A thread can also explicitly terminate itself or terminate any other thread in the process, using a mechanism called cancelation.

Do all threads run before the program exits?

As long as the main-method thread or any other user thread remains alive, your application will continue to execute. In your case, the threads are user threads and hence are allowed to complete before the main thread exits.

How can you tell if a thread is complete C#?

To check the status of the current thread in C#, use the IsAlive property.


2 Answers

You can create a thread static monitor that has a finalizer. When the thread is alive, it will hold the monitor object. When the thead dies it will stop holding it. Later, when GC kicks in, it will finalize your monitor. In the finalizer you can raise an event that will inform your framework about the (observed) death of the client thread.

A sample code can be found in this gist: https://gist.github.com/2587063

Here is a copy of it:

public class ThreadMonitor
{
    public static event Action<int> Finalized = delegate { };
    private readonly int m_threadId = Thread.CurrentThread.ManagedThreadId;

    ~ThreadMonitor()
    {
        Finalized(ThreadId);
    }

    public int ThreadId
    {
        get { return m_threadId; }
    }
}

public static class Test
{
    private readonly static ThreadLocal<ThreadMonitor> s_threadMonitor = 
        new ThreadLocal<ThreadMonitor>(() => new ThreadMonitor());

    public static void Main()
    {
        ThreadMonitor.Finalized += i => Console.WriteLine("thread {0} closed", i);
        var thread = new Thread(() =>
        {
            var threadMonitor = s_threadMonitor.Value;
            Console.WriteLine("start work on thread {0}", threadMonitor.ThreadId);
            Thread.Sleep(1000);
            Console.WriteLine("end work on thread {0}", threadMonitor.ThreadId);
        });
        thread.Start();
        thread.Join();

        // wait for GC to collect and finalize everything
        GC.GetTotalMemory(forceFullCollection: true);

        Console.ReadLine();
    }
}

I hope it helps. I think it's more elegant than your extra waiting thread.

like image 127
Omer Mor Avatar answered Sep 20 '22 12:09

Omer Mor


Since you're not controlling the thread creation directly, it's hard for you to know when the thread has finished doing its work. An alternative approach maybe would be for you to force the client to notify you when they're done:

public interface IThreadCompletedNotifier
{
   event Action ThreadCompleted;
}

public class Library
{
    public void StartSomething(IThreadCompletedNotifier notifier)
    {
        Console.WriteLine("Library says: StartSomething called");
        notifier.ThreadCompleted += () => Console.WriteLine("Libaray says: Client thread existed");
        var clientThread = Thread.CurrentThread;
        exitMonitorThread.Start();
    }
}

This way, any client that calls you is forced to pass in some sort of notification mechanism that will tell you when its done doing its thing:

public class Client : IThreadCompletedNotifier
{
    private readonly Library library;

    public event Action ThreadCompleted;

    public Client(Library library)
    {
        this.library = library;
    }

    public void DoWorkInAThread()
    {
        var thread = new Thread(() =>
        {
            library.StartSomething();
            Thread.Sleep(10);
            Console.WriteLine("Client thread says: I'm done");
            if(ThreadCompleted != null)
            {
               ThreadCompleted();
            }
        });
        thread.Start();
    }
}
like image 22
BFree Avatar answered Sep 19 '22 12:09

BFree