示例#1
0
        //-----------------------------------------------------------------------------------
        // This method is responsible for enumerating results and enqueueing them to
        // the output channel(s) as appropriate.  Each base class implements its own.
        //

        protected override void SpoolingWork()
        {
            // We just enumerate over the entire source data stream, placing each element
            // into the destination channel.
            TInputOutput current   = default(TInputOutput);
            TIgnoreKey   keyUnused = default(TIgnoreKey);

            QueryOperatorEnumerator <TInputOutput, TIgnoreKey> source = _source;
            AsynchronousChannel <TInputOutput> destination            = _destination;
            CancellationToken cancelToken = _groupState.CancellationState.MergedCancellationToken;

            while (source.MoveNext(ref current, ref keyUnused))
            {
                // If an abort has been requested, stop this worker immediately.
                if (cancelToken.IsCancellationRequested)
                {
                    break;
                }

                destination.Enqueue(current);
            }

            // Flush remaining data to the query consumer in preparation for channel shutdown.
            destination.FlushBuffers();
        }
示例#2
0
        //-----------------------------------------------------------------------------------
        // This internal helper method is used to generate a set of asynchronous channels.
        // The algorithm used by each channel contains the necessary synchronizationis to
        // ensure it is suitable for pipelined consumption.
        //
        // Arguments:
        //     partitionsCount - the number of partitions for which to create new channels.
        //
        // Return Value:
        //     An array of asynchronous channels, one for each partition.
        //

        internal static AsynchronousChannel<TInputOutput>[] MakeAsynchronousChannels(int partitionCount, ParallelMergeOptions options, IntValueEvent consumerEvent, CancellationToken cancellationToken)
        {
            AsynchronousChannel<TInputOutput>[] channels = new AsynchronousChannel<TInputOutput>[partitionCount];

            Debug.Assert(options == ParallelMergeOptions.NotBuffered || options == ParallelMergeOptions.AutoBuffered);
            TraceHelpers.TraceInfo("MergeExecutor::MakeChannels: setting up {0} async channels in prep for pipeline", partitionCount);

            // If we are pipelining, we need a channel that contains the necessary synchronization
            // in it. We choose a bounded/blocking channel data structure: bounded so that we can
            // limit the amount of memory overhead used by the query by putting a cap on the
            // buffer size into which producers place data, and blocking so that the consumer can
            // wait for additional data to arrive in the case that it's found to be empty.

            int chunkSize = 0; // 0 means automatic chunk size
            if (options == ParallelMergeOptions.NotBuffered)
            {
                chunkSize = 1;
            }

            for (int i = 0; i < channels.Length; i++)
            {
                channels[i] = new AsynchronousChannel<TInputOutput>(i, chunkSize, cancellationToken, consumerEvent);
            }

            return channels;
        }
示例#3
0
        //-----------------------------------------------------------------------------------
        // Creates, but does not execute, a new spooling task.
        //
        // Arguments:
        //     taskIndex   - the unique index of this task
        //     source      - the producer enumerator
        //     destination - the destination channel into which to spool elements
        //
        // Assumptions:
        //     Source cannot be null, although the other arguments may be.
        //

        internal PipelineSpoolingTask(
            int taskIndex, QueryTaskGroupState groupState,
            QueryOperatorEnumerator <TInputOutput, TIgnoreKey> source, AsynchronousChannel <TInputOutput> destination)
            : base(taskIndex, groupState)
        {
            Contract.Assert(source != null);
            _source      = source;
            _destination = destination;
        }
示例#4
0
        internal static AsynchronousChannel <TInputOutput>[] MakeAsynchronousChannels(int partitionCount, ParallelMergeOptions options, CancellationToken cancellationToken)
        {
            AsynchronousChannel <TInputOutput>[] channelArray = new AsynchronousChannel <TInputOutput> [partitionCount];
            int chunkSize = 0;

            if (options == ParallelMergeOptions.NotBuffered)
            {
                chunkSize = 1;
            }
            for (int i = 0; i < channelArray.Length; i++)
            {
                channelArray[i] = new AsynchronousChannel <TInputOutput>(chunkSize, cancellationToken);
            }
            return(channelArray);
        }
示例#5
0
        private readonly bool _ignoreOutput;                                       // Whether we're enumerating "for effect".

        //-----------------------------------------------------------------------------------
        // Instantiates a new merge helper.
        //
        // Arguments:
        //     partitions   - the source partitions from which to consume data.
        //     ignoreOutput - whether we're enumerating "for effect" or for output.
        //     pipeline     - whether to use a pipelined merge.
        //

        internal DefaultMergeHelper(PartitionedStream <TInputOutput, TIgnoreKey> partitions, bool ignoreOutput, ParallelMergeOptions options,
                                    TaskScheduler taskScheduler, CancellationState cancellationState, int queryId)
        {
            Debug.Assert(partitions != null);

            _taskGroupState = new QueryTaskGroupState(cancellationState, queryId);
            _partitions     = partitions;
            _taskScheduler  = taskScheduler;
            _ignoreOutput   = ignoreOutput;
            IntValueEvent consumerEvent = new IntValueEvent();

            TraceHelpers.TraceInfo("DefaultMergeHelper::.ctor(..): creating a default merge helper");

            // If output won't be ignored, we need to manufacture a set of channels for the consumer.
            // Otherwise, when the merge is executed, we'll just invoke the activities themselves.
            if (!ignoreOutput)
            {
                // Create the asynchronous or synchronous channels, based on whether we're pipelining.
                if (options != ParallelMergeOptions.FullyBuffered)
                {
                    if (partitions.PartitionCount > 1)
                    {
                        Debug.Assert(!ParallelEnumerable.SinglePartitionMode);
                        _asyncChannels =
                            MergeExecutor <TInputOutput> .MakeAsynchronousChannels(partitions.PartitionCount, options, consumerEvent, cancellationState.MergedCancellationToken);

                        _channelEnumerator = new AsynchronousChannelMergeEnumerator <TInputOutput>(_taskGroupState, _asyncChannels, consumerEvent);
                    }
                    else
                    {
                        // If there is only one partition, we don't need to create channels. The only producer enumerator
                        // will be used as the result enumerator.
                        _channelEnumerator = ExceptionAggregator.WrapQueryEnumerator(partitions[0], _taskGroupState.CancellationState).GetEnumerator();
                    }
                }
                else
                {
                    _syncChannels =
                        MergeExecutor <TInputOutput> .MakeSynchronousChannels(partitions.PartitionCount);

                    _channelEnumerator = new SynchronousChannelMergeEnumerator <TInputOutput>(_taskGroupState, _syncChannels);
                }

                Debug.Assert(_asyncChannels == null || _asyncChannels.Length == partitions.PartitionCount);
                Debug.Assert(_syncChannels == null || _syncChannels.Length == partitions.PartitionCount);
                Debug.Assert(_channelEnumerator != null, "enumerator can't be null if we're not ignoring output");
            }
        }
示例#6
0
        protected override void SpoolingWork()
        {
            TInputOutput currentElement = default(TInputOutput);
            TIgnoreKey   currentKey     = default(TIgnoreKey);
            QueryOperatorEnumerator <TInputOutput, TIgnoreKey> source = this.m_source;
            AsynchronousChannel <TInputOutput> destination            = this.m_destination;
            CancellationToken mergedCancellationToken = base.m_groupState.CancellationState.MergedCancellationToken;

            while (source.MoveNext(ref currentElement, ref currentKey))
            {
                if (mergedCancellationToken.IsCancellationRequested)
                {
                    break;
                }
                destination.Enqueue(currentElement);
            }
            destination.FlushBuffers();
        }
示例#7
0
        //-----------------------------------------------------------------------------------
        // The slow path used when a quick loop through the channels didn't come up
        // with anything. We may need to block and/or mark channels as done.
        //

        private bool MoveNextSlowPath()
        {
            int doneChannels = 0;

            // Remember the first channel we are looking at. If we pass through all of the
            // channels without finding an element, we will go to sleep.
            int firstChannelIndex = m_channelIndex;

            int currChannelIndex;

            while ((currChannelIndex = m_channelIndex) != m_channels.Length)
            {
                AsynchronousChannel <T> current = m_channels[currChannelIndex];

                bool isDone = m_done[currChannelIndex];
                if (!isDone && current.TryDequeue(ref m_currentElement))
                {
                    // The channel has an item to be processed. We already remembered the current
                    // element (Dequeue stores it as an out-parameter), so we just return true
                    // after advancing to the next channel.
                    m_channelIndex = (currChannelIndex + 1) % m_channels.Length;
                    return(true);
                }
                else
                {
                    // There isn't an element in the current channel. Check whether the channel
                    // is done before possibly waiting for an element to arrive.
                    if (!isDone && current.IsDone)
                    {
                        // We must check to ensure an item didn't get enqueued after originally
                        // trying to dequeue above and reading the IsDone flag. If there are still
                        // elements, the producer may have marked the channel as done but of course
                        // we still need to continue processing them.
                        if (!current.IsChunkBufferEmpty)
                        {
                            bool dequeueResult = current.TryDequeue(ref m_currentElement);
                            Contract.Assert(dequeueResult, "channel isn't empty, yet the dequeue failed, hmm");
                            return(true);
                        }

                        // Mark this channel as being truly done. We won't consider it any longer.
                        m_done[currChannelIndex] = true;
                        if (m_channelEvents != null)
                        {
                            m_channelEvents[currChannelIndex] = null; //we definitely never want to wait on this (soon to be disposed) event.
                        }

                        isDone = true;
                        current.Dispose();
                    }

                    if (isDone)
                    {
                        Contract.Assert(m_channels[currChannelIndex].IsDone, "thought this channel was done");
                        Contract.Assert(m_channels[currChannelIndex].IsChunkBufferEmpty, "thought this channel was empty");

                        // Increment the count of done channels that we've seen. If this reaches the
                        // total number of channels, we know we're finally done.
                        if (++doneChannels == m_channels.Length)
                        {
                            // Remember that we are done by setting the index past the end.
                            m_channelIndex = currChannelIndex = m_channels.Length;
                            break;
                        }
                    }

                    // Still no element. Advance to the next channel and continue searching.
                    m_channelIndex = currChannelIndex = (currChannelIndex + 1) % m_channels.Length;

                    // If the channels aren't done, and we've inspected all of the queues and still
                    // haven't found anything, we will go ahead and wait on all the queues.
                    if (currChannelIndex == firstChannelIndex)
                    {
                        // On our first pass through the queues, we didn't have any side-effects
                        // that would let a producer know we are waiting. Now we go through and
                        // accumulate a set of events to wait on.
                        try
                        {
                            // If this is the first time we must block, lazily allocate and cache
                            // a list of events to be reused for next time.
                            if (m_channelEvents == null)
                            {
                                m_channelEvents = new ManualResetEventSlim[m_channels.Length];
                            }

                            // Reset our done channels counter; we need to tally them again during the
                            // second pass through.
                            doneChannels = 0;

                            for (int i = 0; i < m_channels.Length; i++)
                            {
                                if (!m_done[i] && m_channels[i].TryDequeue(ref m_currentElement, ref m_channelEvents[i]))
                                {
                                    Contract.Assert(m_channelEvents[i] == null);

                                    // The channel has received an item since the last time we checked.
                                    // Just return and let the consumer process the element returned.
                                    return(true);
                                }
                                else if (m_channelEvents[i] == null)
                                {
                                    // The channel in question is done and empty.
                                    Contract.Assert(m_channels[i].IsDone, "expected channel to be done");
                                    Contract.Assert(m_channels[i].IsChunkBufferEmpty, "expected channel to be empty");

                                    if (!m_done[i])
                                    {
                                        m_done[i] = true;
                                        m_channels[i].Dispose();
                                    }

                                    if (++doneChannels == m_channels.Length)
                                    {
                                        // No need to wait. All channels are done. Remember this by setting
                                        // the index past the end of the channel list.
                                        m_channelIndex = currChannelIndex = m_channels.Length;
                                        break;
                                    }
                                }
                            }

                            // If all channels are done, we can break out of the loop entirely.
                            if (currChannelIndex == m_channels.Length)
                            {
                                break;
                            }

                            // Finally, we have accumulated a set of events. Perform a wait-any on them.
                            Contract.Assert(m_channelEvents.Length <= 63,
                                            "WaitForMultipleObjects only supports 63 threads if running on an STA thread (64 otherwise).");

                            //This WaitAny() does not require cancellation support as it will wake up when all the producers into the
                            //channel have finished.  Hence, if all the producers wake up on cancellation, so will this.
                            m_channelIndex = currChannelIndex = WaitAny(m_channelEvents);
                            Contract.Assert(0 <= m_channelIndex && m_channelIndex < m_channelEvents.Length);
                            Contract.Assert(0 <= currChannelIndex && currChannelIndex < m_channelEvents.Length);
                            Contract.Assert(m_channelEvents[currChannelIndex] != null);

                            //
                            // We have woken up, and the channel that caused this is contained in the
                            // returned index. This could be due to one of two reasons. Either the channel's
                            // producer has notified that it is done, in which case we just have to take it
                            // out of our current wait-list and redo the wait, or a channel actually has an
                            // item which we will go ahead and process.
                            //
                            // We just go back 'round the loop to accomplish this logic. Reset the channel
                            // index and # of done channels. Go back to the beginning, starting with the channel
                            // that caused us to wake up.
                            //

                            firstChannelIndex = currChannelIndex;
                            doneChannels      = 0;
                        }
                        finally
                        {
                            // We have to guarantee that any waits we said we would perform are undone.
                            for (int i = 0; i < m_channelEvents.Length; i++)
                            {
                                // If we retrieved an event from a channel, we need to reset the wait.
                                if (m_channelEvents[i] != null)
                                {
                                    m_channels[i].DoneWithDequeueWait();
                                }
                            }
                        }
                    }
                }
            }

            TraceHelpers.TraceInfo("[timing]: {0}: Completed the merge", DateTime.Now.Ticks);

            // If we got this far, it means we've exhausted our channels.
            Contract.Assert(currChannelIndex == m_channels.Length);

            // If any tasks failed, propagate the failure now. We must do it here, because the merge
            // executor returns control back to the caller before the query has completed; contrast
            // this with synchronous enumeration where we can wait before returning.
            m_taskGroupState.QueryEnd(false);

            return(false);
        }
示例#8
0
        //-----------------------------------------------------------------------------------
        // The slow path used when a quick loop through the channels didn't come up
        // with anything. We may need to block and/or mark channels as done.
        //

        private bool MoveNextSlowPath()
        {
            int doneChannels = 0;

            // Remember the first channel we are looking at. If we pass through all of the
            // channels without finding an element, we will go to sleep.
            int firstChannelIndex = _channelIndex;

            int currChannelIndex;

            while ((currChannelIndex = _channelIndex) != _channels.Length)
            {
                AsynchronousChannel <T> current = _channels[currChannelIndex];

                bool isDone = _done[currChannelIndex];
                if (!isDone && current.TryDequeue(ref _currentElement !)) // TODO-NULLABLE: https://github.com/dotnet/csharplang/issues/2872
                {
                    // The channel has an item to be processed. We already remembered the current
                    // element (Dequeue stores it as an out-parameter), so we just return true
                    // after advancing to the next channel.
                    _channelIndex = (currChannelIndex + 1) % _channels.Length;
                    return(true);
                }
                else
                {
                    // There isn't an element in the current channel. Check whether the channel
                    // is done before possibly waiting for an element to arrive.
                    if (!isDone && current.IsDone)
                    {
                        // We must check to ensure an item didn't get enqueued after originally
                        // trying to dequeue above and reading the IsDone flag. If there are still
                        // elements, the producer may have marked the channel as done but of course
                        // we still need to continue processing them.
                        if (!current.IsChunkBufferEmpty)
                        {
                            bool dequeueResult = current.TryDequeue(ref _currentElement !); // TODO-NULLABLE: https://github.com/dotnet/csharplang/issues/2872
                            Debug.Assert(dequeueResult, "channel isn't empty, yet the dequeue failed, hmm");
                            return(true);
                        }

                        // Mark this channel as being truly done. We won't consider it any longer.
                        _done[currChannelIndex] = true;
                        isDone = true;
                        current.Dispose();
                    }

                    if (isDone)
                    {
                        Debug.Assert(_channels[currChannelIndex].IsDone, "thought this channel was done");
                        Debug.Assert(_channels[currChannelIndex].IsChunkBufferEmpty, "thought this channel was empty");

                        // Increment the count of done channels that we've seen. If this reaches the
                        // total number of channels, we know we're finally done.
                        if (++doneChannels == _channels.Length)
                        {
                            // Remember that we are done by setting the index past the end.
                            _channelIndex = currChannelIndex = _channels.Length;
                            break;
                        }
                    }

                    // Still no element. Advance to the next channel and continue searching.
                    _channelIndex = currChannelIndex = (currChannelIndex + 1) % _channels.Length;

                    // If the channels aren't done, and we've inspected all of the queues and still
                    // haven't found anything, we will go ahead and wait on all the queues.
                    if (currChannelIndex == firstChannelIndex)
                    {
                        // On our first pass through the queues, we didn't have any side-effects
                        // that would let a producer know we are waiting. Now we go through and
                        // accumulate a set of events to wait on.
                        try
                        {
                            // Reset our done channels counter; we need to tally them again during the
                            // second pass through.
                            doneChannels = 0;

                            for (int i = 0; i < _channels.Length; i++)
                            {
                                bool channelIsDone = false;
                                if (!_done[i] && _channels[i].TryDequeue(ref _currentElement !, ref channelIsDone)) // TODO-NULLABLE: https://github.com/dotnet/csharplang/issues/2872
                                {
                                    // The channel has received an item since the last time we checked.
                                    // Just return and let the consumer process the element returned.
                                    return(true);
                                }
                                else if (channelIsDone)
                                {
                                    if (!_done[i])
                                    {
                                        _done[i] = true;
                                    }

                                    if (++doneChannels == _channels.Length)
                                    {
                                        // No need to wait. All channels are done. Remember this by setting
                                        // the index past the end of the channel list.
                                        _channelIndex = currChannelIndex = _channels.Length;
                                        break;
                                    }
                                }
                            }

                            // If all channels are done, we can break out of the loop entirely.
                            if (currChannelIndex == _channels.Length)
                            {
                                break;
                            }

                            Debug.Assert(_consumerEvent != null);
                            //This Wait() does not require cancellation support as it will wake up when all the producers into the
                            //channel have finished.  Hence, if all the producers wake up on cancellation, so will this.
                            _consumerEvent.Wait();
                            _channelIndex = currChannelIndex = _consumerEvent.Value;
                            _consumerEvent.Reset();

                            //
                            // We have woken up, and the channel that caused this is contained in the
                            // returned index. This could be due to one of two reasons. Either the channel's
                            // producer has notified that it is done, in which case we just have to take it
                            // out of our current wait-list and redo the wait, or a channel actually has an
                            // item which we will go ahead and process.
                            //
                            // We just go back 'round the loop to accomplish this logic. Reset the channel
                            // index and # of done channels. Go back to the beginning, starting with the channel
                            // that caused us to wake up.
                            //

                            firstChannelIndex = currChannelIndex;
                            doneChannels      = 0;
                        }
        private bool MoveNextSlowPath()
        {
            int num3;
            int num          = 0;
            int channelIndex = this.m_channelIndex;

            while ((num3 = this.m_channelIndex) != this.m_channels.Length)
            {
                AsynchronousChannel <T> channel = this.m_channels[num3];
                bool flag = this.m_done[num3];
                if (!flag && channel.TryDequeue(ref this.m_currentElement))
                {
                    this.m_channelIndex = (num3 + 1) % this.m_channels.Length;
                    return(true);
                }
                if (!flag && channel.IsDone)
                {
                    if (!channel.IsChunkBufferEmpty)
                    {
                        channel.TryDequeue(ref this.m_currentElement);
                        return(true);
                    }
                    this.m_done[num3] = true;
                    if (this.m_channelEvents != null)
                    {
                        this.m_channelEvents[num3] = null;
                    }
                    flag = true;
                    channel.Dispose();
                }
                if (flag && (++num == this.m_channels.Length))
                {
                    this.m_channelIndex = num3 = this.m_channels.Length;
                    break;
                }
                this.m_channelIndex = num3 = (num3 + 1) % this.m_channels.Length;
                if (num3 == channelIndex)
                {
                    try
                    {
                        if (this.m_channelEvents == null)
                        {
                            this.m_channelEvents = new ManualResetEventSlim[this.m_channels.Length];
                        }
                        num = 0;
                        for (int i = 0; i < this.m_channels.Length; i++)
                        {
                            if (!this.m_done[i] && this.m_channels[i].TryDequeue(ref this.m_currentElement, ref this.m_channelEvents[i]))
                            {
                                return(true);
                            }
                            if (this.m_channelEvents[i] == null)
                            {
                                if (!this.m_done[i])
                                {
                                    this.m_done[i] = true;
                                    this.m_channels[i].Dispose();
                                }
                                if (++num == this.m_channels.Length)
                                {
                                    this.m_channelIndex = num3 = this.m_channels.Length;
                                    break;
                                }
                            }
                        }
                        if (num3 == this.m_channels.Length)
                        {
                            break;
                        }
                        channelIndex = this.m_channelIndex = AsynchronousChannelMergeEnumerator <T> .WaitAny(this.m_channelEvents);

                        num = 0;
                        continue;
                    }
                    finally
                    {
                        for (int j = 0; j < this.m_channelEvents.Length; j++)
                        {
                            if (this.m_channelEvents[j] != null)
                            {
                                this.m_channels[j].DoneWithDequeueWait();
                            }
                        }
                    }
                }
            }
            base.m_taskGroupState.QueryEnd(false);
            return(false);
        }
示例#10
0
 internal PipelineSpoolingTask(int taskIndex, QueryTaskGroupState groupState, QueryOperatorEnumerator <TInputOutput, TIgnoreKey> source, AsynchronousChannel <TInputOutput> destination) : base(taskIndex, groupState)
 {
     this.m_source      = source;
     this.m_destination = destination;
 }