//----------------------------------------------------------------------------------- // 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(); }
//----------------------------------------------------------------------------------- // 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; }
//----------------------------------------------------------------------------------- // 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; }
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); }
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"); } }
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(); }
//----------------------------------------------------------------------------------- // 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); }
//----------------------------------------------------------------------------------- // 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); }
internal PipelineSpoolingTask(int taskIndex, QueryTaskGroupState groupState, QueryOperatorEnumerator <TInputOutput, TIgnoreKey> source, AsynchronousChannel <TInputOutput> destination) : base(taskIndex, groupState) { this.m_source = source; this.m_destination = destination; }