I wanted a class which executes any number of tasks but only a certain amount at the same time (e.g. to download various internet content and keep the overall download speed at a good level). The class I wrote seems to work but there are probably things that can be improved.

1. Do these locks make sense? Should there be other locks as well?
2. I have a method and an event that is only relevant if a specific constructor is used.  Is there a way to improve that?
3. The class I use for tasks is `ThreadStart`.  Is that a good idea?
5. There might be better method names/class names.
4. Are there any general errors (e.g. that more Threads than the max-count will be executed)?

There are probably more points that could be improved.  Also, if anyone knows a good open source library (or native .NET class even) which does just what my class is supposed to do, I would be interested in that too.

    public class ThreadQueue
    {
        private readonly HashSet<ThreadStart> WorkingThreads = new HashSet<ThreadStart>();
        private readonly Queue<ThreadStart> Queue = new Queue<ThreadStart>();

        private bool RaiseCompleteEventIfQueueEmpty = false;

        private int ThreadsMaxCount;

        public ThreadQueue(int threadsMaxCount)
        {
            ThreadsMaxCount = threadsMaxCount;
        }

        /// <summary>
        /// Creates a new thread queue with a maximum number of threads and the tasks that should be executed.
        /// </summary>
        /// <param name="threadsMaxCount">The maximum number of currently active threads.</param>
        /// <param name="tasks">The tasks that should be executed by the queue.</param>
        public ThreadQueue(int threadsMaxCount, ThreadStart[] tasks) : this(threadsMaxCount)
        {
            RaiseCompleteEventIfQueueEmpty = true;
            foreach (ThreadStart task in tasks)
            {
                Queue.Enqueue(task);
            }
        }

        /// <summary>
        /// Starts to execute tasks. Used in conjunction with the constructor in which all tasks are provided.
        /// </summary>
        public void Start()
        {
            CheckQueue();
        }

        private readonly object addlock = new object();
        /// <summary>
        /// Adds a task and runs it if a execution slot is free. Otherwise it will be enqueued.
        /// </summary>
        /// <param name="task">The task that should be executed.</param>
        public void AddTask(ThreadStart task)
        {
            lock (addlock)
            {
                if (WorkingThreads.Count == ThreadsMaxCount)
                {
                    Queue.Enqueue(task);
                }
                else
                {
                    StartThread(task);
                }
            }
        }

        /// <summary>
        /// Starts the execution of a task.
        /// </summary>
        /// <param name="task">The task that should be executed.</param>
        private void StartThread(ThreadStart task)
        {
            WorkingThreads.Add(task);
            BackgroundWorker thread = new BackgroundWorker();
            thread.DoWork += delegate { task.Invoke(); };
            thread.RunWorkerCompleted += delegate { ThreadCompleted(task); };
            thread.RunWorkerAsync();
        }

        private void ThreadCompleted(ThreadStart start)
        {
            WorkingThreads.Remove(start);
            CheckQueue();
            if (Queue.Count == 0 && WorkingThreads.Count == 0 && RaiseCompleteEventIfQueueEmpty) OnCompleted();
        }

        private readonly object checklock = new object();
        /// <summary>
        /// Checks if the queue contains tasks and runs as many as there are free execution slots.
        /// </summary>
        private void CheckQueue()
        {
            lock (checklock)
            {
                while (Queue.Count > 0 && WorkingThreads.Count < ThreadsMaxCount)
                {
                    StartThread(Queue.Dequeue());
                }
                if (Queue.Count == 0 && WorkingThreads.Count == 0 && RaiseCompleteEventIfQueueEmpty) OnCompleted();
            }
        }

        /// <summary>
        /// Raised when all tasks have been completed. Will only be used if the ThreadQueue has been initialized with all the tasks it should execute.
        /// </summary>
        public event EventHandler Completed;

        /// <summary>
        /// Raises the Completed event.
        /// </summary>
        protected void OnCompleted()
        {
            if (Completed != null)
            {
                Completed(this, null);
            }
        }
    }