Note that there are some explanatory texts on larger screens.

plurals
  1. POHow Do I Detect When a Client Thread Exits?
    text
    copied!<p>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).</p> <p>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:</p> <p>First my ‘library’. It grabs the client thread and then creates a new thread which blocks on ‘Join’:</p> <pre><code>public class Library { public void StartSomething() { Console.WriteLine("Library says: StartSomething called"); var clientThread = Thread.CurrentThread; var exitMonitorThread = new Thread(() =&gt; { clientThread.Join(); Console.WriteLine("Libaray says: Client thread existed"); }); exitMonitorThread.Start(); } } </code></pre> <p>Here’s a client that uses my library. It creates a new thread and then calls my library’s StartSomething method:</p> <pre><code>public class Client { private readonly Library library; public Client(Library library) { this.library = library; } public void DoWorkInAThread() { var thread = new Thread(() =&gt; { library.StartSomething(); Thread.Sleep(10); Console.WriteLine("Client thread says: I'm done"); }); thread.Start(); } } </code></pre> <p>When I run the client like this:</p> <pre><code>var client = new Client(new Library()); client.DoWorkInAThread(); // give the client thread time to complete Thread.Sleep(100); </code></pre> <p>I get this output:</p> <pre><code>Library says: StartSomething called Client thread says: I'm done Libaray says: Client thread existed </code></pre> <p>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?</p> <p><strong>First alternative.</strong> </p> <p>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:</p> <pre><code>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"); } } </code></pre> <p>The client is now a little more complicated:</p> <pre><code>public class Client { private readonly Library library; public Client(Library library) { this.library = library; } public void DoWorkInAThread() { var thread = new Thread(() =&gt; { using(var worker = library.GetLibraryWorker()) { worker.StartSomething(); Console.WriteLine("Client thread says: I'm done"); } }); thread.Start(); } } </code></pre> <p>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. </p> <p><strong>Non-breaking second alternative</strong>. 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:</p> <pre><code>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"); } } </code></pre> <p>The client simply puts the StartSomething call in a WorkScope...</p> <pre><code>public class Client { private readonly Library library; public Client(Library library) { this.library = library; } public void DoWorkInAThread() { var thread = new Thread(() =&gt; { using(library.GetWorkScope()) { library.StartSomething(); Console.WriteLine("Client thread says: I'm done"); } }); thread.Start(); } } </code></pre> <p>I like this less than the first alternative because it doesn't force the library user to think about scope.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload