/// <summary>
        /// Consumer thread that pulls values from the numbers queue and determines if they are prime
        /// </summary>
        /// <param name="state">Contains the id of the thread</param>
        static void ConsumerThread(object state)
        {
            int id = (int)state;

            UpdateThreadStatus(id, false, '+');
            List <ulong> primes = m_Primes[id];

            while (m_ProducersComplete.WaitOne(1) == false || m_Numbers.Count > 0)
            {
                ulong number;
                if (m_Numbers.TryDequeue(out number))
                {
                    UpdateThreadStatus(id, false, 'X');
                    if (MathStuff.IsPrime(number))
                    {
                        primes.Add(number);
                    }
                    UpdateThreadStatus(id, false, '+');
                } // end of if
            }     // end of while

            UpdateThreadStatus(id, false, '-');
        } // end of method
        /// <summary>
        /// Main program
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Console.BufferHeight = 9999;
            ulong highest             = IOFunctions.GetULongFromUser("Enter largest number to test for prime: ", 2, 100000000UL);
            int   producerThreadCount = IOFunctions.GetIntFromUser("Enter number of producer threads (1 - 64): ", 1, 64);
            int   consumerThreadCount = IOFunctions.GetIntFromUser("Enter number of consumer threads (1 - 64): ", 1, 64);

            OutputHeader();
            ulong         approxNumPrimes = MathStuff.ApproximateNumberOfPrimes(highest);
            List <Thread> producerThreads = new List <Thread>();
            List <Thread> consumerThreads = new List <Thread>();

            DateTime startTime = DateTime.Now;

            // Create the producer threads
            for (int i = 0; i < producerThreadCount; i++)
            {
                Thread producer = new Thread(ProducerThread);
                producer.Start(new Tuple <int, int, ulong>(i, producerThreadCount, highest));
                producerThreads.Add(producer);
            }

            // Create the consumer threads
            for (int i = 0; i < consumerThreadCount; i++)
            {
                Thread consumer = new Thread(ConsumerThread);
                // Create the List of ulongs that the consumer will place its results into
                // Also, use the approx. # of primes to minimize list resizing
                List <ulong> primes =
                    new List <ulong>((int)(approxNumPrimes / (uint)consumerThreadCount));
                // Add that list to m_Primes, using the thread's ID as its key
                m_Primes.Add(i, primes);
                consumer.Start((int)i);
                consumerThreads.Add(consumer);
            }

            // Create the timer that will output the number of numbers left to process
            m_Timer = new Timer(CountTimerCallback, null, 500, 500);

            // Wait for producer threads...
            foreach (Thread thread in producerThreads)
            {
                thread.Join();
            }
            // Signal to all consumers that no more values will be produced
            m_ProducersComplete.Set();

            // Wait for consumer threads...
            foreach (Thread thread in consumerThreads)
            {
                thread.Join();
            }


            m_Timer.Dispose();

            DateTime endTime = DateTime.Now;

            CountTimerCallback(null);
            Console.SetCursorPosition(0, 8);
            Console.WriteLine("Processing took: {0} ms", endTime.Subtract(startTime).TotalMilliseconds);

            ShowStats();

            ShowPrimes(highest);

            Console.WriteLine();
            Console.Write("Press <ENTER> to quit...");
            Console.ReadLine();
        }