-1

Sorry if this was asked before. I have two almost identical functions sync and async:

    private static void runLong(int id)
    {
        Console.WriteLine("starting " + id );
        Thread.Sleep(myRandom.Next(200,1000));
        Console.WriteLine("-exiting " + id);
    }
    private static async Task runLongAsync(int id)
    {
        Console.WriteLine("starting " + id );
        await Task.Run(()=>Thread.Sleep(myRandom.Next(200,1000)));
        Console.WriteLine("-exiting " + id );
    }

I call them in a very similar way:

    public static void run()
    {
            run().Wait();
            runAsync().Wait();
    }
    private static async Task run()
    {
        List<Task> runningTasks = new List<Task>();
        for (int i = 0; i < 5; i++)
        {
            runningTasks.Add(Task.Run(() => runLong(i)));  
        }
        await Task.WhenAll(runningTasks);
    }
    private static async Task runAsync()
    {
        List<Task> runningTasks = new List<Task>();
        for (int i = 0; i < 5; i++)
        {
            runningTasks.Add(runLongAsync(i));   
        }
        await Task.WhenAll(runningTasks);
    }

However result is different: Calling synchronous function asynchronously

  • starting 5
  • starting 5
  • starting 5
  • starting 5
  • starting 5
  • exiting 5
  • exiting 5
  • exiting 5
  • exiting 5
  • exiting 5

Calling asynchronous function:

  • starting 0
  • starting 1
  • starting 2
  • starting 3
  • starting 4
  • exiting 0
  • exiting 3
  • exiting 2
  • exiting 1
  • exiting 4

I.E. in first case parameters are overwritten but in the second- they are not. What's going on here?

Edit1: Yes I found a quick fix int copy = i; But I'd like to understand what's actually going on here and why these two functions behave differently.

Edit2: Articles referred by @Servy explain why for and foreach loops behave differently, in my case I have two for loops that behave differently

shlasasha
  • 165
  • 1
  • 14
  • @Servy I'd like to understand what's going on here not just a quick fix **int copy = i;** like in the thread you suggested. – shlasasha Apr 04 '18 at 15:45
  • So then did you read the two linked articles with more detailed explanations of why this is behaving in this way? – Servy Apr 04 '18 at 15:48
  • I'm sorry but I don't understand how it explains why in one case int is passed to the function by value and in other int is passed by reference – shlasasha Apr 04 '18 at 16:22
  • So, since you read those posts, you understand how closure semantics work for the snippet that uses a closure, and you just don't understand how calling a method normally without using closures passes values? Or what is it that you don't understand? – Servy Apr 04 '18 at 17:10

1 Answers1

0

Captured variables. The i here is not per loop iteration, so it will pick up the current value when it gets launched - which will probably be 5:

for (int i = 0; i < 5; i++)
{
    runningTasks.Add(Task.Run(() => runLong(i)));  
}

A workaround is to hoist the value inside the loop:

for (int i = 0; i < 5; i++)
{
    var j = i;
    runningTasks.Add(Task.Run(() => runLong(j)));  
}

The important point here is that j is now scoped inside the loop, so the captured variable context is specific to that iteration.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900