ThreadPool throttle
07 June 05 08:01 PM | psheill | 1 Comments   

As promised, here are the details on how we limit the number of threads being used to perform a background task.  The code below was written by Brian Harry. With this class, we can queue up as many requests as we want, but only maxThreads number of them will be active at any given time.  So long as maxThreads is smaller than the size of the system thread pool, threads remain available for high-priority tasks such as serving ASP.NET pages or web requests.  Given that there is only one thread pool per application in .NET, this class provides a good substitute for isolated thread pools. 

using System;
using System.Collections.Generic;
using System.Threading;

namespace ThreadPoolThrottle
{
    class ThreadPoolThrottle
    {
        // Initialize the throttler to the specified # of max threads.
        public ThreadPoolThrottle(int maxThreads)
        {
            m_maxThreads = maxThreads;
            m_callback = new WaitCallback(ProcessWorkItem);
        }

        /// <summary>
        /// The thread priority at which thread pool threads dispatched to handle work items
        /// for this throttle class will run.
        /// </summary>
        public ThreadPriority Priority
        {
            get { return m_priority; }
            set { m_priority = value; }
        }

        /// <summary>
        /// Queue a work item to the threadpool while obeying the thread limit passed in the
        /// constructor.  If this instance of the ThreadPoolThrottle already has submitted
        /// its limit of simultaneous tasks to the threadpool, wait for an existing work
        /// item to complete and submit this request when there is room.
        /// </summary>
        /// <param name="callBack">The callback to be called on the threadpool thread.</param>
        /// <param name="state">A state object to be supplied on the callback.</param>
        /// <returns>True if the request was successfully queued and false if not.</returns>
        public bool QueueUserWorkItemWithWait(WaitCallback callBack, Object state)
        {
            // Loop in the case that a race condition happens that causes us to not see space in
            // the thread pool without the lock and then find space with the lock.
            for (;;)
            {
                // Lightweight check to see if there is room in our limit to start this work item.
                if (m_currentThreads < m_maxThreads)
                {
                    // Allocate a slot.
                    if (Interlocked.Increment(ref m_currentThreads) <= m_maxThreads)
                    {
                        // If we succeeded in getting a slot under the limit, queue the item to
                        // the thread pool.
                        return ThreadPool.UnsafeQueueUserWorkItem(m_callback, new ThrottleCallbackState(callBack, state));
                    }

                    // We failed to get a slot so return it and try with a lock.
                    Interlocked.Decrement(ref m_currentThreads);
                }

                lock (m_queue)
                {
                    // Check under the lock if the thread limit is still full.
                    if (m_currentThreads >= m_maxThreads)
                    {
                        // If it is, mark us as waiting and go to sleep until we are awoken.
                        ++m_waiting;
                        Monitor.Wait(m_queue);
                        --m_waiting;

                        // Once we have been awoken, run our work item - the thread that woke us
                        // did not decrement m_currentThreads so we don't need to increment it.
                        return ThreadPool.UnsafeQueueUserWorkItem(m_callback, new ThrottleCallbackState(callBack, state));
                    }
                }
            }
        }

        /// <summary>
        /// Queue a work item to the threadpool while obeying the thread limit passed in the
        /// constructor.  If this instance of the ThreadPoolThrottle already has submitted
        /// its limit of simultaneous tasks to the threadpool, queue this work item and return.
        /// </summary>
        /// <param name="callBack">The callback to be called on the threadpool thread.</param>
        /// <param name="state">A state object to be supplied on the callback.</param>
        /// <returns>True if the request was successfully queued and false if not.</returns>
        public bool QueueUserWorkItem(WaitCallback callBack, Object state)
        {
            for (;;)
            {
                // Lightweight check to see if there is room in our limit to start this work item.
                if (m_currentThreads < m_maxThreads)
                {
                    // Allocate a slot.
                    if (Interlocked.Increment(ref m_currentThreads) <= m_maxThreads)
                    {
                        // If we succeeded in getting a slot under the limit, queue the item to
                        // the thread pool.
                        return ThreadPool.UnsafeQueueUserWorkItem(m_callback, new ThrottleCallbackState(callBack, state));
                    }

                    // We failed to get a slot so return it and try with a lock.
                    Interlocked.Decrement(ref m_currentThreads);
                }

                lock (m_queue)
                {
                    // Check under the lock if the thread limit is still full.
                    if (m_currentThreads >= m_maxThreads)
                    {
                        try
                        {
                            // Put the work item on the queue.
                            m_queue.Enqueue(new ThrottleCallbackState(callBack, state));
                            return true;
                        }
                        catch
                        {
                            return false;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// An approximate count of the number of queued work items that are being processed or
        /// waiting to be processed.
        /// </summary>
        public int UncompletedWorkItems
        {
            get { return m_currentThreads + m_waiting + m_queue.Count; }
        }

        // Callback funtion from the thread pool to process a work item.  We need this so that
        // we can keep track of the number that are currently being processed and limit it to
        // the requested number.
        private void ProcessWorkItem(Object state)
        {
            ThrottleCallbackState callbackState = (ThrottleCallbackState)state;

            // Call the queued callback and pass its state.
            CouldntQueue:
            try
            {
                if (m_priority != ThreadPriority.Normal)
                {
                    Thread.CurrentThread.Priority = m_priority;
                }
                callbackState.m_callback(callbackState.m_state);
            }
            finally
            {
                if (Thread.CurrentThread.Priority != ThreadPriority.Normal)
                {
                    Thread.CurrentThread.Priority = ThreadPriority.Normal;
                }
            }

            lock (m_queue)
            {
                if (m_waiting > 0)
                {
                    // This will wake up a thread that will take our spot in the "current"
                    // threads list so we don't need to decrement m_currentThreads.
                    Monitor.Pulse(m_queue);
                }
                else if (m_queue.Count > 0)
                {
                    // Dequeue a waiting item and queue it back to the thread pool.  Since
                    // we are queuing it, we don't need to decrement m_currentThreads.
                    callbackState = m_queue.Dequeue();
                    if (!ThreadPool.UnsafeQueueUserWorkItem(m_callback, callbackState))
                    {
                        // If we weren't able to queue the next item, rather than drop it on
                        // the floor or let threads drain out when they shouldn't, run it
                        // manually.
                        goto CouldntQueue;
                    }
                }
                else
                {
                    // There are no work items waiting to process, so reduce the number of
                    // working threads.
                    Interlocked.Decrement(ref m_currentThreads);
                }
            }
        }

        int m_maxThreads;
        int m_currentThreads;
        ThreadPriority m_priority = ThreadPriority.Normal;
        WaitCallback m_callback;
        int m_waiting;
        Queue<ThrottleCallbackState> m_queue = new Queue<ThrottleCallbackState>(32);

        // In internal class to track the user's callback and state.
        class ThrottleCallbackState
        {
            public ThrottleCallbackState(WaitCallback callback, Object state)
            {
                m_callback = callback;
                m_state = state;
            }

            internal WaitCallback m_callback;
            internal Object m_state;
        }
    }
}

The CPU is free, how come my server isn’t responding to requests?
12 May 05 11:08 AM | psheill | 0 Comments   

You’ve written your web application using all the latest features of .NET.  Because some tasks should be done in the background, you invoke them by calling BeginInvoke on a delegate, asking the thread pool to run the task asynchronously.  That should have a minute effect on the rest of the system, right?

Not necessarily.  You may see, like we have, a different kind of contention.  It’s not threads competing for CPU time.  It’s requests competing for the thread pool.  The thread pool has a fixed maximum number of threads, to minimize the amount of time the system spends context switching.  The default maximum is 25 threads per CPU.

There is one shared thread pool per process.  ASP.NET uses threads from the pool to service web requests. Requests to the thread pool all have equal priority – they are granted in a first-come first-served way.  So if you have made 25 requests for background tasks, the system won’t begin the 26th request until one of them completes.  It can’t – the threads are all occupied.  If that 26th request is an ASP.NET web request, too bad, it will have to wait its turn.

“So why is the CPU only at 50%” you may ask.  Those threads won’t necessarily be using the CPU.   Most enterprise applications today are I/O-bound, spending most of their time waiting for the disk or the network.  It’s not uncommon to see 25 threads all waiting for I/O at the same time. 

So be on the watch for this situation if you use the thread pool in .NET.  Look to see if the number of threads in your process has maxed out. 

Later I’ll give some details on how we solved the problem for our system.

WinDirStat - free disk usage tool
10 May 05 10:03 AM | psheill | 0 Comments   

One more excellent free tool is WinDirStat.  It shows both where the disk space is going, as well as what it is going toward, based on file type, in a clean graphical way.  In just a few clicks you can clean up gigabytes of unneeded data.  I found many .pdb files on my machine I didn't need.  Even if you have a huge hard drive, fewer files means faster defragmentation and faster virus scans.  Highly recommended.

 

Windows per-user special folder creation
17 April 05 01:25 PM | psheill | 0 Comments   

I needed to learn how the "special" folders are created for a user account, usually under C:\Documents and Settings\<username>.  How does a new user have the right to create files under "Documents and Settings" anyways?  Does he need to be an admin on the box?  To answer these questions, I fired up FileMon and invoked the "runas" command to effectively log in as a new user.  Then the answer was clear.  The "winlogon.exe" program, running under the System account, creates the folders.  The System account has full rights to the system, enabling the creation of the new folders. Then rights are granted to the user.  Naturally, the user doesn't need to be an admin.  FileMon is mighty handy.

XML Serializing a Hashtable or generic Dictionary
09 April 05 12:11 PM | psheill | 5 Comments   

The XmlSerializer in .NET has many good qualities.  It creates output that is understandable and not overly verbose.  It works with many data types.  It has a simple, sensible policy -- it only serializes public properties and members.  It doesn't require special attributes.  The only thing that bothers me is that it doesn't serialize Hashtables or Dictionaries.  Here is some code that enables serializing and deserializing those types by using an intermediary List.  I haven't tried it using a non-generic ArrayList or array, but I believe that would work too.

        using System.Collections.Generic;
    using System.Collections;
    using System.IO;
    using System.Xml.Serialization;
    using System.Xml;
    using System;
        public static void Serialize(TextWriter writer, IDictionary dictionary)
        {
            List<Entry> entries = new List<Entry>(dictionary.Count);
            foreach (object key in dictionary.Keys)
            {
                entries.Add(new Entry(key, dictionary[key]));
            }
            XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
            serializer.Serialize(writer, entries);
        }
        public static void Deserialize(TextReader reader, IDictionary dictionary)
        {
            dictionary.Clear();
            XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
            List<Entry> list = (List<Entry>)serializer.Deserialize(reader);
            foreach (Entry entry in list)
            {
                dictionary[entry.Key] = entry.Value;
            }
        }
        public class Entry
        {
            public object Key;
            public object Value;
            public Entry()
            {
            }
           
            public Entry(object key, object value)
            {
                Key = key;
                Value = value;
            }
        }

It generates output like the following, when the keys and values are strings.

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Entry>
    <Key xsi:type="xsd:string">MyKey</Key>
    <Value xsi:type="xsd:string">MyValue</Value> 
  </Entry>
  <Entry>   
    <Key xsi:type="xsd:string">MyOtherKey</Key>   
    <Value xsi:type="xsd:string">MyOtherValue</Value> 
  </Entry>
</ArrayOfEntry>

 
More Posts « Previous page

Search

This Blog

Syndication

Page view tracker