36

How would I structure the code below so that the async method gets invoked?

Parallel.For(0, elevations.Count(), delegate(int i)
{
   allSheets.AddRange(await BuildSheetsAsync(userID, elevations[i], includeLabels));
});
Indy9000
  • 8,651
  • 2
  • 32
  • 37

4 Answers4

49

Parallel.For() doesn't work well with async methods. If you don't need to limit the degree of parallelism (i.e. you're okay with all of the tasks executing at the same time), you can simply start all the Tasks and then wait for them to complete:

var tasks = Enumerable.Range(0, elevations.Count())
    .Select(i => BuildSheetsAsync(userID, elevations[i], includeLabels));
List<Bitmap> allSheets = (await Task.WhenAll(tasks)).SelectMany(x => x).ToList();
svick
  • 236,525
  • 50
  • 385
  • 514
  • I'm not able to add the collection of List it is asking for IEnnumerable. how to do i get this to work? var task = Enumerable.Range(0, elevations.Length).Select(i => BuildSheetsAsync(userID, elevations[i], includeLabels)); List allSheets = new List(); allSheets.AddRange(await Task.WhenAll(task)); The error occurs when I allSheets.AddRange(await Task.WhenAll(task)); – Filling The Stack is What I DO Oct 10 '13 at 16:45
  • @AlumCloud.Com Ah, I didn't notice `BuildSheetsAsync()` already returns a collection. If you want to make a single collection out of a collection of collections, you can use `SelectMany()`. And to make a list out of an `IEnumerable` collection, use `ToList()`. – svick Oct 10 '13 at 16:50
  • 2
    "*Parallel.For() doesn't work well with async methods.*" - Why shouldn't they work well together? They are two different concepts that help increase CPU efficiency. "*If you don't need to limit the degree of parallelism...*" - It seems counter-productive to make this assumption. A good program would purposely limit the degree of parallelism based on the capabilities of the server it's running on, which could change. – Sean Oct 17 '18 at 17:36
  • 4
    @Sean They don't work well together, because with `await`, processing ends when the returned `Task` is completed, not when the function returns. But `Parallel.For` does not understand that and assumes that a function ends when it returns. And limiting the degree of parallelism can be important, but it can also be complicated. If I don't know whether the complicated approach is necessary, I recommend starting with the simple one. – svick Oct 17 '18 at 18:08
  • 1
    How would you go about setting a limit a la maximum degree of parallelism as you would with Parallel.For? – Dinerdo Nov 09 '18 at 22:16
  • For `MaxDegreeOfParallelism` problems see [this answer](https://stackoverflow.com/a/11565317/542251) – Liam Jan 29 '19 at 14:10
6

You can try this code I'm using. it using foreach and SemaphoreSlim to achive parallel asynchronous.

public static class ParallelAsync
{
    public static async Task ForeachAsync<T>(IEnumerable<T> source, int maxParallelCount, Func<T, Task> action)
    {
        using (SemaphoreSlim completeSemphoreSlim = new SemaphoreSlim(1))
        using (SemaphoreSlim taskCountLimitsemaphoreSlim = new SemaphoreSlim(maxParallelCount))
        {
            await completeSemphoreSlim.WaitAsync();
            int runningtaskCount = source.Count();

            foreach (var item in source)
            {
                await taskCountLimitsemaphoreSlim.WaitAsync();

                Task.Run(async () =>
                {
                    try
                    {
                        await action(item).ContinueWith(task =>
                        {
                            Interlocked.Decrement(ref runningtaskCount);
                            if (runningtaskCount == 0)
                            {
                                completeSemphoreSlim.Release();
                            }
                        });
                    }
                    finally
                    {
                        taskCountLimitsemaphoreSlim.Release();
                    }
                }).GetHashCode();
            }

            await completeSemphoreSlim.WaitAsync();
        }
    }
}

usage:

string[] a = new string[] {
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    "10",
    "11",
    "12",
    "13",
    "14",
    "15",
    "16",
    "17",
    "18",
    "19",
    "20"
};

Random random = new Random();

await ParallelAsync.ForeachAsync(a, 2, async item =>
{
    Console.WriteLine(item + " start");

    await Task.Delay(random.Next(1500, 3000));
    Console.WriteLine(item + " end");
});

Console.WriteLine("All finished");

any suggestion please let me know.

Alex Zhang
  • 1,070
  • 11
  • 15
3

I'd recommend you to take a look at this question I asked a few days ago and ended-up answering myself, basically I was looking for a parallel and asynchronous ForEach method.

The method uses SemaphoreSlim to process things in parallel and it accepts asynchronous methods as an input action.

You might also want to take a look at the two links I have provided at the end of my answer, they have been really helpful for realizing such behavior and they also contain another way of doing this using a Partitioner instead.

Personally, I didn't like the Parallel.For because it's a synchronous call as explained in the links I've given; I wanted it all 'async' :-)

Here it is : Asynchronously and parallelly downloading files

Community
  • 1
  • 1
aybe
  • 15,516
  • 9
  • 57
  • 105
  • 1
    +1, besides `SemaphoreSlim` apparently is not required in the OP's case. Also may I suggest using `CancellationTokenSource` with timeout instead of `Timer` object in your code. – noseratio Oct 10 '13 at 03:50
-7

The easiest way to invoke your async method inside Parallel.For is next:

Parallel.For(0, elevations.Count(), async i =>
{
   allSheets.AddRange(await BuildSheetsAsync(userID, elevations[i], includeLabels));
});

==============

MarioDS mentioned absolutely right in the comment that in that case you may have unobserved exceptions. And this is definitely very important thing which you should always take in mind then have a deal with async delegates.

In this case if you think that you will have exceptions you can use try/catch block inside delegate. Or in some cases if your situation is good for it you can subscribe on TaskScheduler.UnobservedTaskException event.

Yury Kerbitskov
  • 643
  • 1
  • 7
  • 21
  • 16
    This leads to bugs. Async delegates return `Task` and the Parallel construct doesn't await that task. This means exceptions are unobserved and you can't be sure after the `Parallel.For` invocation has run that all the work has actually been completed (nor can you verify easily). – MarioDS Oct 03 '17 at 12:49