private async Task ExecuteWithPolicy(SubscriptionInfo info, Func <Task> action, Action <Exception> abort, Action ignore = null) { int attempts = 0; bool retry = true; do { try { attempts++; await action().ConfigureAwait(false); retry = false; } catch (Exception exception) { ExceptionResolution resolution = await ExceptionHandler(exception, attempts, info).ConfigureAwait(false); switch (resolution) { case ExceptionResolution.Abort: abort(exception); retry = false; break; case ExceptionResolution.Retry: break; case ExceptionResolution.Ignore: retry = false; ignore?.Invoke(); break; } } }while (retry); }
/// <summary> /// Main thingy. /// </summary> /// <remarks> /// This method will not run in parallel with itself. /// </remarks> /// <exception cref="AggregateException"> /// Thrown when: An exception occurs on your processor method and: Either (1) your ExceptionHandler throws an exception, or (2) you did not provide ExceptionHandler. /// </exception> /// <exception cref="OperationCanceledException"> /// Thrown when: your <see cref="CancellationToken"/> is triggered. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown when: you attempt to run this method in parallel. /// Or: the main <see cref="IEnumerable{T}"/> collection was modified in mid-iteration. /// </exception> public async Task <RunResult> Run(CancellationToken token) { #region Failsafes lock (_syncRoot) { if (_isRunning) { throw new InvalidOperationException("You cannot parallel this method."); } _isRunning = true; } #endregion try { ExceptionResolution resolution = ExceptionResolution.Swallow; List <Exception> unhandledExceptions = new List <Exception>(); #region Main Cycle foreach (T item in _collection) { //T itemCopy = item; IDisposable gateReleaser = await _gate.WaitAsync(token).ConfigureAwait(false); WorkItem workItem = new WorkItem { CTS = CancellationTokenSource.CreateLinkedTokenSource(token), GateReleaser = gateReleaser }; lock (_syncRoot) { _currentTasks.Add(workItem); } workItem.Task = Task.Run(async() => { Interlocked.Increment(ref _stats._started); try { await _method(item, workItem.CTS.Token).ConfigureAwait(false); Interlocked.Increment(ref _stats._completed); } catch (OperationCanceledException) when(token.IsCancellationRequested) { // The user should expect this exception to be thrown when they trigger their token. Interlocked.Increment(ref _stats._canceled); } catch (OperationCanceledException) when(workItem.CTS.Token.IsCancellationRequested) { // We triggered the cancellation. Interlocked.Increment(ref _stats._canceled); } catch (Exception ex) { Interlocked.Increment(ref _stats._failed); if (_exceptionHandler == null) { lock (_syncRoot) { unhandledExceptions.Add(ex); } } else { try { ExceptionResolution r = await _exceptionHandler(item, ex, workItem.CTS.Token).ConfigureAwait(false); Interlocked.Increment(ref _stats._exceptionsCaught); lock (_syncRoot) { resolution = (ExceptionResolution)Math.Max((int)resolution, (int)r); } if (r == ExceptionResolution.Swallow) { Interlocked.Increment(ref _stats._exceptionsSwallowed); } } catch (OperationCanceledException) when(workItem.CTS.Token.IsCancellationRequested) { // Swallowed because the iteration is shutting down. } catch (Exception resEx) { lock (_syncRoot) { unhandledExceptions.Add(resEx); } } } } finally { // Some probably avoidable heavy stuff. lock (_syncRoot) { _currentTasks.Remove(workItem); workItem.GateReleaser.Dispose(); workItem.CTS.Dispose(); } } }, token); token.ThrowIfCancellationRequested(); if (unhandledExceptions.Count > 0 || resolution != ExceptionResolution.Swallow) { break; } } #endregion token.ThrowIfCancellationRequested(); #region Dealing with uncaught exceptions lock (_syncRoot) { if (unhandledExceptions.Count > 0) { throw new AggregateException(unhandledExceptions); } } #endregion // Copy the value so that Exceptions thrown within processors do not change this value. ExceptionResolution finalResolution = resolution; #region Dealing with caught exceptions if (finalResolution != ExceptionResolution.Swallow) { if (finalResolution != ExceptionResolution.Abandon) { if (finalResolution != ExceptionResolution.SoftStop) { lock (_syncRoot) { foreach (WorkItem workItem in _currentTasks.ToArray()) { workItem.CTS.Cancel(); } } } if (finalResolution != ExceptionResolution.Forget) { await AwaitRemainingTasks(token).ConfigureAwait(false); } } lock (_syncRoot) { if (unhandledExceptions.Count > 0) { throw new AggregateException(unhandledExceptions); } } return(RunResult.Interrupted); } #endregion await AwaitRemainingTasks(token).ConfigureAwait(false); } finally { _isRunning = false; } return(RunResult.Finished); }