views:

65

answers:

3

I have a C# class library which starts up a background consumer thread (lazily) which listens for tasks to complete from a producer/consumer queue. This class library can be used from any type of .NET application and is currently in use under an ASP.NET MVC web site. The consumer thread is blocked most of the time until a request comes into the queue. There should be only one consumer thread for any given app domain.

I want to be able to gracefully shut down the consumer thread when the application exits, regardless of what type of application it is, e.g. Windows Forms, Console, or WPF application. The application code itself should be ignorant of the fact that this thread is lingering around waiting to be terminated.

How can I solve this thread lifetime issue from the class library's perspective? Is there some global application shutdown event I can tie into to tear down the thread gracefully then?

Right now, since it's in an ASP.NET MVC app domain, it really doesn't matter since those never get torn down gracefully anyway. But now that I'm starting to use it in scheduled console application tasks designed to terminate, I want to solve this problem once and for all. The console application does not terminate since the consumer thread is still active and blocked waiting for requests. I've exposed a public static Thread property on the class to issue an Abort() call when the console app is exiting but this is, quite frankly, disgusting.

Any pointers would be appreciated! Again, I don't want to have to write Windows Forms or WPF or console application specific code to solve the issue. A nice generic solution that every consumer of the class library can use would be best.

+2  A: 

Set the thread's IsBackground property to true. Then it will not prevent the process from ending.

http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx

Alternately, instead of using Abort, use Interrupt. Much less disgusting.

Steven Sudit
That's what I'm looking for! Yes, I know `Abort()` is disgusting; thanks for the tip on `Interrupt()`!
James Dunne
You, sir, have made my day. Thank you very much! No need for the `Abort()` or `Interrupt()` call anymore.
James Dunne
Hmya, it still gets aborted of course, even though you don't actually call Abort() yourself. The effect is identical.
Hans Passant
@Hans: As usual, you are correct. However, marking it as a background thread is still cleaner, since it shows the intent of the thread.
Steven Sudit
+1  A: 

If you are using .net 4.0 a CancelationToken can be used to signal the thread that it is time to gracefully shut down. It is very useful as most Wait commands allow you to pass it a token and if the thread is in a wait when the thread is canceled it will automatically come out of the wait.

Scott Chamberlain
Not using .NET 4.0 yet, but thanks for the info.
James Dunne
This is on par with `Interrupt`, but a bit nicer.
Steven Sudit
+2  A: 

This is a bit tricky. You are right on track that you want to avoid an abort as that might terminate the thread in the middle of an important operation (writing to a file, etc.). Setting the IsBackground property would have the same effect.

What you need to do is make your blocking queue cancellable. Too bad you are not using .NET 4.0 yet otherwise you could have used the BlockingCollection class. No worries though. You will just have to modify your custom queue so that it polls periodically for a stopping signal. Here is a quick and dirty implementation that I just whipped up using the canonical implementation of a blocking queue as a starting point.

public class CancellableBlockingCollection<T>
{
    private Queue<T> m_Queue = new Queue<T>();

    public T Take(WaitHandle cancel)
    {
        lock (m_Queue)
        {
            while (m_Queue.Count <= 0)
            {
                if (!Monitor.Wait(m_Queue, 1000))
                {
                    if (cancel.WaitOne(0))
                    {
                        throw new ThreadInterruptedException();
                    }
                }
            }
            return m_Queue.Dequeue();
        }
    }

    public void Add(T data)
    {
        lock (m_Queue)
        {
            m_Queue.Enqueue(data);
            Monitor.Pulse(m_Queue);
        }
    }
}

Notice how the Take method accepts a WaitHandle that can be tested for the cancel signal. This could be the same wait handle used in other parts of your code. The idea here is that the wait handle gets polled on a certain interval inside Take and if it is signaled then the whole method throws. The assumption is that throwing inside Take is okay since it is at a safe point because the consumer could not have been in the middle of an important operation. You can just allow the thread to disintegrate upon the exception at this point. Here is what your consumer thread may look like.

ManualResetEvent m_Cancel = new ManualResetEvent(false);
CancellableBlockingCollection<object> m_Queue = new CancellableBlockingCollection<object>();

private void ConsumerThread()
{
  while (true)
  {
    object item = m_Queue.Take(m_Cancel); // This will throw if the event is signalled.
  }
}

There are various other ways you could cancel the Take method. I chose to use a WaitHandle, but it could easily be done using a simple bool flag for example.

Update:

As Steven pointed out in the comments Thread.Interrupt does essentially this already. It will cause a thread to throw an exception on a blocking call...pretty much what I was after in this example, but with a lot more code. One caveat with Thread.Interrupt is that it only works during the canned blocking calls in the .NET BCL (like WaitOne, etc.). So you would not be able to cancel a long running computation in progress like you would if you used a more manual approach like the one in this answer. It is definitely great tool to keep in your back pocket and might just be useful in your specific scenario.

Brian Gideon
You can "cancel" a wait by using `Thread.Interrupt`, and it doesn't have the horrible side-effects of `Thread.Abort`.
Steven Sudit
@Steven: Indeed!
Brian Gideon
In response to your update, one possibility might be to do a `WaitOne(0)` periodically. This should trigger the interrupt exception, without actually blocking. Having said that, I believe it may well yield the core.
Steven Sudit
@Steven: Good idea. And I agree that `Thread.Interrupt` is probably the way to go in this scenario.
Brian Gideon
I could make my queue thread cancellable, but for its purpose it doesn't matter very much. It's a queue for storing items into memcached. So if something doesn't get into the cache before the app ends, "big whoop wanna fight about it?" lol :)
James Dunne
@James: Yes, in your case, just making it a background thread seems sufficient.
Steven Sudit
In that case `Thread.Interrupt` would be fine. In fact, `Thread.Abort` would probably be fine as well. All the horrible state corruption that occurs after the abort would be irrelevant if the process was being torn down anyway. I know...it still has that dirty feeling to it though :)
Brian Gideon