4

I'm writing a Windows service and am looking for a way to execute a number of foreach loops in parallel, where each loop makes a call to an asynchronous (TAP) method. I initially tried the following code, which doesn't work because Parallel.ForEach and async/await are not compatible. Does anyone know whether there is an alternate approach that can achieve this?

Parallel.ForEach(messagesByFromNumber, async messageGroup =>
{
    foreach (var message in messageGroup)
    {
        await message.SendAsync();
    }
});

For the sake of clarity, due to the way that SendAsync() operates, each copy of the foreach loop must execute serially; in other words, the foreach loop cannot become concurrent / parallel.

3
  • I'm not entirely convinced you would need the parallel at all since the async call is a fire and forget type method. Just do a standard for each and fire the async method should do something very close to what you want it to do Commented Dec 31, 2014 at 19:15
  • @theDarse The reason I couldn't use a standard foreach as the outer loop is because the SendAsync() method is so slow that I needed the outer loop to have the chance to run on separate threads. Doing two nested standard foreach loops would take too long. Commented Dec 31, 2014 at 19:39
  • Don't mix parallel and async code. Use async when you don't want to burn a thread (usually I/O calls), Parallel is for CPU intensive calculations to execute on all cores in parallel. Commented Jan 1, 2015 at 14:37

2 Answers 2

5

There's no need to use Parallel.Foreach if your goal is to run these concurrently. Simply go over all your groups, create a task for each group that does a foreach of SendAsync, get all the tasks, and await them all at once with Task.WhenAll:

var tasks = messagesByFromNumber.Select(async messageGroup =>
{
    foreach (var message in messageGroup)
    {
        await message.SendAsync();
    }
});
await Task.WhenAll(tasks)
Sign up to request clarification or add additional context in comments.

2 Comments

I had no idea that async could be used in the select statement that way. Just tried your solution and the code ran as desired. Thanks so much!
@JeffGlazier sure... any time.
0

You can make it more cleat with AsyncEnumerator NuGet Package:

using System.Collections.Async;

await messagesByFromNumber.ParallelForEachAsync(async messageGroup =>
{
    foreach (var message in messageGroup)
    {
        await message.SendAsync();
    }
}, maxDegreeOfParallelism: 10);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.