2
  • static Task<int> GetPrimeCountAsync(int start, int stop) is for counting the number of primes in a closed interval [start,stop] where 2 < start <= stop.

  • static void WrapperAsync() is for printing a list of 10 executions of GetPrimeCountAsync, each with a closed interval spanning from i == 0 ? 3 : 1000_000 * i to 1000_000 * (i + 1) - 1, where i spinning from 0 to 9. Inside this method, there are 3 cases, only one is activated.

    1. when case 1 is activated, the result is as follows:

      enter image description here

    2. when case 2 is activated, the result is as follows:

      enter image description here

    3. when case 3 is activated, the result is as follows:

      enter image description here

Questions

If we compare case 1, 2 and 3, there are the same glitches in case 1 and 2 but not in case 3. The case 3 is the wanted result.

Actually the glitches can be removed by buffering the loop counter with local variables as follows:

int start = i == 0 ? 3 : 1000_000 * i;
int stop = 1000_000 * (i + 1) - 1;

And then just pass start and stop to GetPrimeCountAsync and WriteLine.

The question is: What causes these 2 glitches in case 1 and 2?

Complete Code

using System;
using System.Linq;
using System.Threading.Tasks;
using static System.Console;


class Program
{
    static void Main()
    {
        WrapperAsync();
        WriteLine("Ended...");
    }


    static void WrapperAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            //int start = i == 0 ? 3 : 1000_000 * i;
            //int stop = 1000_000 * (i + 1) - 1;

            // case 1: OnCompleted
            var awaiter = GetPrimeCountAsync(i == 0 ? 3 : 1000_000 * i, 1000_000 * (i + 1) - 1).GetAwaiter();
            awaiter.OnCompleted(() => WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {1000_000 * (i + 1) - 1} is {awaiter.GetResult()}"));

            // case 2: ContinueWith
            //var task = GetPrimeCountAsync(i == 0 ? 3 : 1000_000 * i, (i + 1) * 1000_000 - 1);
            //task.ContinueWith(t => WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {(i + 1) * 1000_000 - 1} is {t.GetAwaiter().GetResult()}"));

            // case 3: without OnCompleted and without ContinueWith
            //var task = GetPrimeCountAsync(i == 0 ? 3 : 1000_000 * i, (i + 1) * 1000_000 - 1);
            //WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {(i + 1) * 1000_000 - 1} is {task.GetAwaiter().GetResult()}");
        }
    }

    //  Note that 2 < start <= stop 
    static Task<int> GetPrimeCountAsync(int start, int stop)
    {
        var count = ParallelEnumerable.Range(start, stop - start + 1)
                    .Where(i => i % 2 > 0)
                    .Count(j => Enumerable.Range(3, (int)Math.Sqrt(j) - 1).Where(k => k % 2 > 0).All(l => j % l > 0));

        return Task.Run(() => count);
    }
}
Second Person Shooter
  • 14,188
  • 21
  • 90
  • 165

1 Answers1

0

The "glitches" you described come from the fact that the for loop uses one variable. In awaiter.OnCompleted(() => WriteLine($"The number of primes between {(i == 0 ? 3 : 1000_000 * i)} and {1000_000 * (i + 1) - 1} is {awaiter.GetResult()}")); the lambda you pass to OnCompleted is a closure over i in the for loop. It means that it uses the current value of i and not the one from the time you passed it to the function. You can see that behaviour when you do

List<Action> actions = new List<Action>();
for(int i = 0; i < 10; ++i)
    actions.Add(()=>Console.WriteLine(i + 1))

foreach(Action a in actions) 
    a();

It prints 10 10 times. To make it print numbers from 1 to 10 you'd have to do

List<Action> actions = new List<Action>();
for(int i = 0; i < 10; ++i)
{
    int temp = i;
    actions.Add(()=>Console.WriteLine(temp + 1))
} 

foreach(Action a in actions) 
    a();

This Stack Overflow question talks about closures in more detail.

This is the reason why if you use

int start = i == 0 ? 3 : 1000_000 * i;
int stop = 1000_000 * (i + 1) - 1;

you get what you wanted. Without it the task takes more time than one iteration of the loop, so it uses the next value of i.


There's actually something else you should do differently. Async methods should use the async keyword, so your signatures should look like

static async Task<int> GetPrimeCountAsync(int start, int stop)

and

static async Task WrapperAsync() //notice the Task return type

You shouldn't use GetAwaiter, use await instead. You can read about it here. So instead of

return Task.Run(() => count);

you can now write

return count;

Leave the harder stuff for the compiler.

If you want to have it all done asynchronously you should use only the second option with a little change:

var task = GetPrimeCountAsync(start, stop)
    .ContinueWith(t => WriteLine($"The number of primes between {start} and {stop} is {t.Result}"));

If you want them to execute in order you can write

await GetPrimeCountAsync(start, stop)
    .ContinueWith(t => WriteLine($"The number of primes between {start} and {stop} is {t.Result}"));

in the loop instead. Other than that, If you want to print everything before Ended... you can write

await WrapperAsync(); //in c# 7
//or
WrapperAsync().Wait();

in Main. Actually I think you have to do it, because the application can terminate when it reaches the end of Main without waiting for all the tasks to complete. If you don't require Ended... to be written after all the tasks you can do

var wrapperTask = WrapperAsync();
WriteLine("Ended...");
await wrapperTask;
//or wrapperTask.Wait();

Complete code should look more or less like this

using System;
using System.Linq;
using System.Threading.Tasks;
using static System.Console;


class Program
{
    //since c# 7
    static async Task Main()
    {
        //use this or other options I provided
        await WrapperAsync();
        WriteLine("Ended...");
    }

    //earlier versions of c#
    static void Main() 
    {
        WrapperAsync().Wait();
        WriteLine("Ended...");
    }


    static async Task WrapperAsync()
    {
        for (int i = 0; i < 10; i++)
        {
            int start = i == 0 ? 3 : 1000_000 * i;
            int stop = 1000_000 * (i + 1) - 1;

            //you can also change it here to do exactly what you want
            await GetPrimeCountAsync(start, stop)
                .ContinueWith(t => WriteLine($"The number of primes between {start} and {stop} is {t.Result}"));
        }
    }

    //  Note that 2 < start <= stop 
    static async Task<int> GetPrimeCountAsync(int start, int stop)
    {
        var count = ParallelEnumerable.Range(start, stop - start + 1)
                    .Where(i => i % 2 > 0)
                    .Count(j => Enumerable.Range(3, (int)Math.Sqrt(j) - 1).Where(k => k % 2 > 0).All(l => j % l > 0));

        return count;
    }
}
Jakub Dąbek
  • 1,044
  • 1
  • 8
  • 17