/// <summary> /// Processes given inputs with a fallback for failures /// </summary> /// <remarks> /// * Call first function in given inputs /// * Retry failures with fallback function /// * Fix indices for results of fallback function /// * Merge the results /// </remarks> public static async Task <IEnumerable <Task <Indexed <TResult> > > > RunWithFallback <TSource, TResult>( IReadOnlyList <TSource> inputs, GetIndexedResults <TSource, TResult> initialFunc, GetIndexedResults <TSource, TResult> fallbackFunc, Func <TResult, bool> isSuccessFunc, Func <IReadOnlyList <Indexed <TResult> >, Task> initialSuccessTaskFunc = null ) { // Get results from first method IEnumerable <Task <Indexed <TResult> > > initialResults = await initialFunc(inputs); // Determine hits / misses based on given isSuccessFunc ILookup <bool, Indexed <TResult> > resultLookup = await initialResults.ToLookupAwait(r => isSuccessFunc(r.Item)); IReadOnlyList <Indexed <TResult> > indexedSuccesses = resultLookup[true].ToList(); IReadOnlyList <Indexed <TResult> > indexedFailures = resultLookup[false].ToList(); // Optional action to process hits from first attempt if (initialSuccessTaskFunc != null) { await initialSuccessTaskFunc(indexedSuccesses); } // Return early if no misses if (indexedFailures.Count == 0) { return(indexedSuccesses.AsTasks()); } // Try fallback for items that failed in first attempt IReadOnlyList <TSource> missedInputs = indexedFailures.Select(r => inputs[r.Index]).ToList(); IEnumerable <Task <Indexed <TResult> > > fallbackResults = await fallbackFunc(missedInputs); // Fix indices for fallback results to corresponding indices from original input IList <Indexed <TResult> > fixedFallbackResults = new List <Indexed <TResult> >(missedInputs.Count); foreach (var resultTask in fallbackResults) { Indexed <TResult> result = await resultTask; int originalIndex = indexedFailures[result.Index].Index; fixedFallbackResults.Add(result.Item.WithIndex(originalIndex)); } // Merge original successful results with fallback results return(indexedSuccesses .Concat(fixedFallbackResults) .AsTasks()); }
/// <summary> /// Processes given inputs with on first level then optionally calls subset for second level /// </summary> /// <remarks> /// * Call first function in given inputs /// * Call specified inputs (based on first function results) with second function /// * Fix indices for results of second function /// * Merge the results /// </remarks> public static async Task <IEnumerable <Task <Indexed <TResult> > > > RunMultiLevelAsync <TSource, TResult>( IReadOnlyList <TSource> inputs, GetIndexedResults <TSource, TResult> runFirstLevelAsync, GetIndexedResults <TSource, TResult> runSecondLevelAsync, Func <TResult, bool> useFirstLevelResult, Func <IReadOnlyList <Indexed <TResult> >, Task> handleFirstLevelOnlyResultsAsync = null ) { // Get results from first method IEnumerable <Task <Indexed <TResult> > > initialResults = await runFirstLevelAsync(inputs); // Determine which inputs can use the first level results based on useFirstLevelResult() List <Indexed <TResult> > indexedFirstLevelOnlyResults = new List <Indexed <TResult> >(); List <int> nextLevelIndices = null; foreach (var resultTask in initialResults) { var result = await resultTask; if (useFirstLevelResult(result.Item)) { indexedFirstLevelOnlyResults.Add(result); } else { nextLevelIndices = nextLevelIndices ?? new List <int>(); nextLevelIndices.Add(result.Index); } } // Optional action to process hits from first attempt if (handleFirstLevelOnlyResultsAsync != null) { await handleFirstLevelOnlyResultsAsync(indexedFirstLevelOnlyResults); } // Return early if no misses if (nextLevelIndices == null) { return(initialResults); } // Try fallback for items that failed in first attempt IReadOnlyList <TSource> missedInputs = nextLevelIndices.Select(index => inputs[index]).ToList(); IEnumerable <Task <Indexed <TResult> > > fallbackResults = await runSecondLevelAsync(missedInputs); // Fix indices for fallback results to corresponding indices from original input IList <Indexed <TResult> > fixedFallbackResults = new List <Indexed <TResult> >(missedInputs.Count); foreach (var resultTask in fallbackResults) { Indexed <TResult> result = await resultTask; int originalIndex = nextLevelIndices[result.Index]; fixedFallbackResults.Add(result.Item.WithIndex(originalIndex)); } // Merge original successful results with fallback results return(indexedFirstLevelOnlyResults .Concat(fixedFallbackResults) .AsTasks()); }