/// <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; } }
internal static void ThrowOCEorAggregateException(Exception ex, CancellationState cancellationState) { if (!ThrowAnOCE(ex, cancellationState)) { throw new AggregateException(new Exception[] { ex }); } CancellationState.ThrowWithStandardMessageIfCanceled(cancellationState.ExternalCancellationToken); }
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); }
//----------------------------------------------------------------------------------- // 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. } }