예제 #1
0
        /// <summary>
        /// Opens the query and initializes _openedQueryEnumerator and _querySettings.
        /// Called from the first MoveNext call.
        /// </summary>
        private void OpenQuery()
        {
            // Avoid opening (and failing) twice.. not only would it be bad to re-enumerate some elements, but
            // the cancellation/disposed flags are most likely stale.
            if (_hasQueryOpeningFailed)
            {
                throw new InvalidOperationException(SR.PLINQ_EnumerationPreviouslyFailed);
            }

            try
            {
                // stuff in appropriate defaults for unspecified options.
                _querySettings = _queryOperator.SpecifiedQuerySettings
                                 .WithPerExecutionSettings(_topLevelCancellationTokenSource, _topLevelDisposedFlag)
                                 .WithDefaults();

                QueryLifecycle.LogicalQueryExecutionBegin(_querySettings.QueryId);

                _openedQueryEnumerator = _queryOperator.GetOpenedEnumerator(
                    _mergeOptions, _suppressOrderPreservation, false, _querySettings);


                // Now that we have opened the query, and got our hands on a supplied cancellation token
                // we can perform an early cancellation check so that we will not do any major work if the token is already canceled.
                CancellationState.ThrowWithStandardMessageIfCanceled(_querySettings.CancellationState.ExternalCancellationToken);
            }
            catch
            {
                _hasQueryOpeningFailed = true;
                throw;
            }
        }
예제 #2
0
 internal static void ThrowOCEorAggregateException(Exception ex, CancellationState cancellationState)
 {
     if (!ThrowAnOCE(ex, cancellationState))
     {
         throw new AggregateException(new Exception[] { ex });
     }
     CancellationState.ThrowWithStandardMessageIfCanceled(cancellationState.ExternalCancellationToken);
 }
예제 #3
0
        public bool MoveNext()
        {
            if (_topLevelDisposedFlag.Value)
            {
                throw new ObjectDisposedException("enumerator", SR.PLINQ_DisposeRequested);
            }


            //Note: if Dispose has been called on a different thread to the thread that is enumerating,
            //then there is a race condition where _openedQueryEnumerator is instantiated but not disposed.
            //Best practice is that Dispose() should only be called by the owning thread, hence this cannot occur in correct usage scenarios.

            // Open the query operator if called for the first time.

            if (_openedQueryEnumerator == null)
            {
                // To keep the MoveNext method body small, the code that executes first time only is in a separate method.
                // It appears that if the method becomes too large, we observe a performance regression. This may have
                // to do with method inlining.
                OpenQuery();
            }

            bool innerMoveNextResult = _openedQueryEnumerator.MoveNext();

            // This provides cancellation-testing for the consumer-side of the buffers that appears in each scenario:
            //   Non-order-preserving (defaultMergeHelper)
            //       - asynchronous channel (pipelining)
            //       - synchronous channel  (stop-and-go)
            //   Order-preserving (orderPreservingMergeHelper)
            //       - internal results buffer.
            // This moveNext is consuming data out of buffers, hence the inner moveNext is expected to be very fast.
            // => thus we only test for cancellation per-N-iterations.
            // NOTE: the cancellation check occurs after performing moveNext in case the cancellation caused no data
            //       to be produced.. We need to ensure that users sees an OCE rather than simply getting no data. (see Bug702254)
            if ((_moveNextIteration & CancellationState.POLL_INTERVAL) == 0)
            {
                CancellationState.ThrowWithStandardMessageIfCanceled(
                    _querySettings.CancellationState.ExternalCancellationToken);
            }

            _moveNextIteration++;
            return(innerMoveNextResult);
        }
 private void OpenQuery()
 {
     if (this.m_hasQueryOpeningFailed)
     {
         throw new InvalidOperationException(System.Linq.SR.GetString("PLINQ_EnumerationPreviouslyFailed"));
     }
     try
     {
         this.m_querySettings = this.m_queryOperator.SpecifiedQuerySettings.WithPerExecutionSettings(this.m_topLevelCancellationTokenSource, this.m_topLevelDisposedFlag).WithDefaults();
         QueryLifecycle.LogicalQueryExecutionBegin(this.m_querySettings.QueryId);
         this.m_openedQueryEnumerator = this.m_queryOperator.GetOpenedEnumerator(this.m_mergeOptions, this.m_suppressOrderPreservation, false, this.m_querySettings);
         CancellationState.ThrowWithStandardMessageIfCanceled(this.m_querySettings.CancellationState.ExternalCancellationToken);
     }
     catch
     {
         this.m_hasQueryOpeningFailed = true;
         throw;
     }
 }
        public bool MoveNext()
        {
            if (this.m_topLevelDisposedFlag.Value)
            {
                throw new ObjectDisposedException("enumerator", System.Linq.SR.GetString("PLINQ_DisposeRequested"));
            }
            if (this.m_openedQueryEnumerator == null)
            {
                this.OpenQuery();
            }
            bool flag = this.m_openedQueryEnumerator.MoveNext();

            if ((this.m_moveNextIteration & 0x3f) == 0)
            {
                CancellationState.ThrowWithStandardMessageIfCanceled(this.m_querySettings.CancellationState.ExternalCancellationToken);
            }
            this.m_moveNextIteration++;
            return(flag);
        }
예제 #6
0
        //-----------------------------------------------------------------------------------
        // Marks the end of a query's execution, waiting for all tasks to finish and
        // propagating any relevant exceptions.  Note that the full set of tasks must have
        // been initialized (with SetTask) before calling this.
        //

        internal void QueryEnd(bool userInitiatedDispose)
        {
            Debug.Assert(_rootTask != null);
            //Debug.Assert(Task.Current == null || (Task.Current != _rootTask && Task.Current.Parent != _rootTask));

            if (Interlocked.Exchange(ref _alreadyEnded, 1) == 0)
            {
                // There are four cases:
                // Case #1: Wait produced an exception that is not OCE(ct), or an AggregateException which is not full of OCE(ct) ==>  We rethrow.
                // Case #2: External cancellation has been requested ==> we'll manually throw OCE(externalToken).
                // Case #3a: We are servicing a call to Dispose() (and possibly also external cancellation has been requested).. simply return.
                // Case #3b: The enumerator has already been disposed (and possibly also external cancellation was requested).  Throw an ODE.
                // Case #4: No exceptions or explicit call to Dispose() by this caller ==> we just return.

                // See also "InlinedAggregationOperator" which duplicates some of this logic for the aggregators.
                // See also "QueryOpeningEnumerator" which duplicates some of this logic.
                // See also "ExceptionAggregator" which duplicates some of this logic.

                try
                {
                    // Wait for all the tasks to complete
                    // If any of the tasks ended in the Faulted stated, an AggregateException will be thrown.
                    _rootTask.Wait();
                }
                catch (AggregateException ae)
                {
                    AggregateException flattenedAE = ae.Flatten();
                    bool allOCEsOnTrackedExternalCancellationToken = true;
                    for (int i = 0; i < flattenedAE.InnerExceptions.Count; i++)
                    {
                        OperationCanceledException?oce = flattenedAE.InnerExceptions[i] as OperationCanceledException;

                        // we only let it pass through iff:
                        // it is not null, not default, and matches the exact token we were given as being the external token
                        // and the external Token is actually canceled (i.e. not a spoof OCE(extCT) for a non-canceled extCT)
                        if (oce == null ||
                            !oce.CancellationToken.IsCancellationRequested ||
                            oce.CancellationToken != _cancellationState.ExternalCancellationToken)
                        {
                            allOCEsOnTrackedExternalCancellationToken = false;
                            break;
                        }
                    }

                    // if all the exceptions were OCE(externalToken), then we will propagate only a single OCE(externalToken) below
                    // otherwise, we flatten the aggregate (because the WaitAll above already aggregated) and rethrow.
                    if (!allOCEsOnTrackedExternalCancellationToken || flattenedAE.InnerExceptions.Count == 0)
                    {
                        throw flattenedAE;  // Case #1
                    }
                }
                finally
                {
                    //_rootTask don't support Dispose on some platforms
                    (_rootTask as IDisposable)?.Dispose();
                }

                if (_cancellationState.MergedCancellationToken.IsCancellationRequested)
                {
                    // cancellation has occurred but no user-delegate exceptions were detected

                    // NOTE: it is important that we see other state variables correctly here, and that
                    // read-reordering hasn't played havoc.
                    // This is OK because
                    //   1. all the state writes (e,g. in the Initiate* methods) are volatile writes (standard .NET MM)
                    //   2. tokenCancellationRequested is backed by a volatile field, hence the reads below
                    //   won't get reordered about the read of token.IsCancellationRequested.

                    // If the query has already been disposed, we don't want to throw an OCE
                    if (!_cancellationState.TopLevelDisposedFlag.Value)
                    {
                        CancellationState.ThrowWithStandardMessageIfCanceled(_cancellationState.ExternalCancellationToken); // Case #2
                    }

                    //otherwise, given that there were no user-delegate exceptions (they would have been rethrown above),
                    //the only remaining situation is user-initiated dispose.
                    Debug.Assert(_cancellationState.TopLevelDisposedFlag.Value);

                    // If we aren't actively disposing, that means somebody else previously disposed
                    // of the enumerator. We must throw an ObjectDisposedException.
                    if (!userInitiatedDispose)
                    {
                        throw new ObjectDisposedException("enumerator", SR.PLINQ_DisposeRequested); // Case #3
                    }
                }
                // Case #4. nothing to do.
            }
        }