Nesting await in Parallel.ForEach

后端 未结 9 1335
别跟我提以往
别跟我提以往 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:41

    Using DataFlow as svick suggested may be overkill, and Stephen's answer does not provide the means to control the concurrency of the operation. However, that can be achieved rather simply:

    public static async Task RunWithMaxDegreeOfConcurrency<T>(
         int maxDegreeOfConcurrency, IEnumerable<T> collection, Func<T, Task> taskFactory)
    {
        var activeTasks = new List<Task>(maxDegreeOfConcurrency);
        foreach (var task in collection.Select(taskFactory))
        {
            activeTasks.Add(task);
            if (activeTasks.Count == maxDegreeOfConcurrency)
            {
                await Task.WhenAny(activeTasks.ToArray());
                //observe exceptions here
                activeTasks.RemoveAll(t => t.IsCompleted); 
            }
        }
        await Task.WhenAll(activeTasks.ToArray()).ContinueWith(t => 
        {
            //observe exceptions in a manner consistent with the above   
        });
    }
    

    The ToArray() calls can be optimized by using an array instead of a list and replacing completed tasks, but I doubt it would make much of a difference in most scenarios. Sample usage per the OP's question:

    RunWithMaxDegreeOfConcurrency(10, ids, async i =>
    {
        ICustomerRepo repo = new CustomerRepo();
        var cust = await repo.GetCustomer(i);
        customers.Add(cust);
    });
    

    EDIT Fellow SO user and TPL wiz Eli Arbel pointed me to a related article from Stephen Toub. As usual, his implementation is both elegant and efficient:

    public static Task ForEachAsync<T>(
          this IEnumerable<T> source, int dop, Func<T, Task> body) 
    { 
        return Task.WhenAll( 
            from partition in Partitioner.Create(source).GetPartitions(dop) 
            select Task.Run(async delegate { 
                using (partition) 
                    while (partition.MoveNext()) 
                        await body(partition.Current).ContinueWith(t => 
                              {
                                  //observe exceptions
                              });
    
            })); 
    }
    
    0 讨论(0)
  • 2020-11-22 01:45

    You can save effort with the new AsyncEnumerator NuGet Package, which didn't exist 4 years ago when the question was originally posted. It allows you to control the degree of parallelism:

    using System.Collections.Async;
    ...
    
    await ids.ParallelForEachAsync(async i =>
    {
        ICustomerRepo repo = new CustomerRepo();
        var cust = await repo.GetCustomer(i);
        customers.Add(cust);
    },
    maxDegreeOfParallelism: 10);
    

    Disclaimer: I'm the author of the AsyncEnumerator library, which is open source and licensed under MIT, and I'm posting this message just to help the community.

    0 讨论(0)
  • 2020-11-22 01:48

    I am a little late to party but you may want to consider using GetAwaiter.GetResult() to run your async code in sync context but as paralled as below;

     Parallel.ForEach(ids, i =>
    {
        ICustomerRepo repo = new CustomerRepo();
        // Run this in thread which Parallel library occupied.
        var cust = repo.GetCustomer(i).GetAwaiter().GetResult();
        customers.Add(cust);
    });
    
    0 讨论(0)
提交回复
热议问题