Esempio n. 1
0
        /// <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());
        }