开发者

Background consumer thread lifetime management best practices

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. Th开发者_运维问答ere 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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜