Nesting await in Parallel.ForEach

后端 未结 9 1331
别跟我提以往
别跟我提以往 2020-11-22 01:01

In a metro app, I need to execute a number of WCF calls. There are a significant number of calls to be made, so I need to do them in a parallel loop. The problem is that th

9条回答
  •  悲哀的现实
    2020-11-22 01:29

    After introducing a bunch of helper methods, you will be able run parallel queries with this simple syntax:

    const int DegreeOfParallelism = 10;
    IEnumerable result = await Enumerable.Range(0, 1000000)
        .Split(DegreeOfParallelism)
        .SelectManyAsync(async i => await CalculateAsync(i).ConfigureAwait(false))
        .ConfigureAwait(false);
    

    What happens here is: we split source collection into 10 chunks (.Split(DegreeOfParallelism)), then run 10 tasks each processing its items one by one (.SelectManyAsync(...)) and merge those back into a single list.

    Worth mentioning there is a simpler approach:

    double[] result2 = await Enumerable.Range(0, 1000000)
        .Select(async i => await CalculateAsync(i).ConfigureAwait(false))
        .WhenAll()
        .ConfigureAwait(false);
    

    But it needs a precaution: if you have a source collection that is too big, it will schedule a Task for every item right away, which may cause significant performance hits.

    Extension methods used in examples above look as follows:

    public static class CollectionExtensions
    {
        /// 
        /// Splits collection into number of collections of nearly equal size.
        /// 
        public static IEnumerable> Split(this IEnumerable src, int slicesCount)
        {
            if (slicesCount <= 0) throw new ArgumentOutOfRangeException(nameof(slicesCount));
    
            List source = src.ToList();
            var sourceIndex = 0;
            for (var targetIndex = 0; targetIndex < slicesCount; targetIndex++)
            {
                var list = new List();
                int itemsLeft = source.Count - targetIndex;
                while (slicesCount * list.Count < itemsLeft)
                {
                    list.Add(source[sourceIndex++]);
                }
    
                yield return list;
            }
        }
    
        /// 
        /// Takes collection of collections, projects those in parallel and merges results.
        /// 
        public static async Task> SelectManyAsync(
            this IEnumerable> source,
            Func> func)
        {
            List[] slices = await source
                .Select(async slice => await slice.SelectListAsync(func).ConfigureAwait(false))
                .WhenAll()
                .ConfigureAwait(false);
            return slices.SelectMany(s => s);
        }
    
        /// Runs selector and awaits results.
        public static async Task> SelectListAsync(this IEnumerable source, Func> selector)
        {
            List result = new List();
            foreach (TSource source1 in source)
            {
                TResult result1 = await selector(source1).ConfigureAwait(false);
                result.Add(result1);
            }
            return result;
        }
    
        /// Wraps tasks with Task.WhenAll.
        public static Task WhenAll(this IEnumerable> source)
        {
            return Task.WhenAll(source);
        }
    }
    

提交回复
热议问题