How to correctly write Parallel.For with async methods

35,031

Solution 1

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();

Solution 2

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

Solution 3

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.

Share:
35,031
Filling The Stack is What I DO
Author by

Filling The Stack is What I DO

I specialize in complex software solutions for the construction industry, specifically CAD drafting and automation architectural solutions. However, I do not limit myself to this one specific industry, I love coding and it is my passion thus I will take on any challenge. Erik Little Bits Builder [email protected] 469-540-8417

Updated on October 16, 2020

Comments

  • Filling The Stack is What I DO
    Filling The Stack is What I DO over 3 years

    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));
    });