/// <summary>
        /// Creates and begins execution of a new set of spooling tasks.
        /// </summary>
        public static void Spool(
            QueryTaskGroupState groupState, PartitionedStream <TOutput, TKey> partitions,
            bool[] consumerWaiting, bool[] producerWaiting, bool[] producerDone,
            Queue <Pair <TKey, TOutput> >[] buffers, object[] bufferLocks,
            TaskScheduler taskScheduler, bool autoBuffered)
        {
            Contract.Requires(groupState != null);
            Contract.Requires(partitions != null);
            Contract.Requires(producerDone != null && producerDone.Length == partitions.PartitionCount);
            Contract.Requires(buffers != null && buffers.Length == partitions.PartitionCount);
            Contract.Requires(bufferLocks != null);

            int degreeOfParallelism = partitions.PartitionCount;

            // Initialize the buffers and buffer locks.
            for (int i = 0; i < degreeOfParallelism; i++)
            {
                buffers[i]     = new Queue <Pair <TKey, TOutput> >(OrderPreservingPipeliningMergeHelper <TOutput, TKey> .INITIAL_BUFFER_SIZE);
                bufferLocks[i] = new object();
            }

            // Ensure all tasks in this query are parented under a common root. Because this
            // is a pipelined query, we detach it from the parent (to avoid blocking the calling
            // thread), and run the query on a separate thread.
            Task rootTask = new Task(
                () =>
            {
                for (int i = 0; i < degreeOfParallelism; i++)
                {
                    QueryTask asyncTask = new OrderPreservingPipeliningSpoolingTask <TOutput, TKey>(
                        partitions[i], groupState, consumerWaiting, producerWaiting,
                        producerDone, i, buffers, bufferLocks[i], taskScheduler, autoBuffered);
                    asyncTask.RunAsynchronously(taskScheduler);
                }
            });

            // Begin the query on the calling thread.
            groupState.QueryBegin(rootTask);

            // And schedule it for execution.  This is done after beginning to ensure no thread tries to
            // end the query before its root task has been recorded properly.
            rootTask.Start(taskScheduler);

            // We don't call QueryEnd here; when we return, the query is still executing, and the
            // last enumerator to be disposed of will call QueryEnd for us.
        }
        //-----------------------------------------------------------------------------------
        // Schedules execution of the merge itself.
        //

        void IMergeHelper <TOutput> .Execute()
        {
            OrderPreservingPipeliningSpoolingTask <TOutput, TKey> .Spool(
                m_taskGroupState, m_partitions, m_consumerWaiting, m_producerWaiting, m_producerDone,
                m_buffers, m_bufferLocks, m_taskScheduler, m_autoBuffered);
        }