0

I have a .NET 3.1 Core app. I am learning to use the Task Parallel library. To do this, I want to create a basic Task. I want to run this Task via an extension method. At this time, I have the following:

var tasks = new List<Task<int>>();       // Used to track the calculated result
var details = new List<Details>();       // Used for debugging purposes

var random = new Random();               // Used to generate a random wait time
var offset = 10;                         // This value is used to perform a basic calculation

// The following tasks should run the tasks OUT OF ORDER (i.e. 4, 5, 2, 3, 1)
var stopwatch = Stopwatch.StartNew();    // Keep track of how long all five calculations take
for (var i=1; i<=5; i++)
{
  var shouldBe = i + offset;
  var d = new Details();

  var t = new Task<int>(() => {
    var ms = random.Next(1001);    // Choose a wait time between 0 and 1 seconds
    Thread.Sleep(ms);
    d.SleepTime = ms;

    var result = i + offset;
    Console.WriteLine($"{i} + {offset} = {result}  (over {timeout} ms.)");
    return result;
  });

  tasks.Add(t.ExecuteAsync<int>());
  details.Add(d);
}
stopwatch.Stop();

Task.WaitAll(tasks.ToArray());
foreach (var t in tasks)
{
  Console.WriteLine($"Result: {t.Result}");
}

// Calculate how long the whole thing took
var totalMilliseconds = 0.0;
foreach (var log in logs)
{
  totalMilliseconds = totalMilliseconds + log.TotalMilliseconds;
}
Console.WriteLine($"Runtime: {stopwatch.Elapsed.TotalMilliseconds}, Execution: {totalMilliseconds}");

MyExtensions.cs

namespace System.Threading.Tasks
{
  public static class MyExtensions
  {
    public static async Task<T> ExecuteAsync<T>(this Task<T> code, Details details)
    {
      T result = default(T);

      Stopwatch stopwatch = Stopwatch.StartNew();
      code.Start();
      await code.ConfigureAwait(false);

      result = code.Result;
      stopwatch.Stop();

      return result;
    }
  }

  // The following class is only for debugging purposes
  public class Details
  {
     public int SleepTime { get; set; }

     public double TotalMilliseconds { get; set; }
  }
}

When I run this app, I see something like the following in the Terminal window:

6 + 10 = 16 (over 44 ms.)
6 + 10 = 16 (over 197 ms.)
6 + 10 = 16 (over 309 ms.)
6 + 10 = 16 (over 687 ms.)
6 + 10 = 16 (over 950 ms.)
Result: 16
Result: 16
Result: 16
Result: 16
Result: 16
Runtime: 3.5835, Execution: 2204.62970000004

Based on this output, it appears I have five tasks running asynchronously. The sleep time changes each time (which is good). The runtime is less than the execution time, which implies the tasks are running in parallel. However, it's almost like the tasks get ran after the 'for' loop has finished. That's the only place I can see where the 6 is coming from. But, I don't understand why, or how to fix it. The results should be more like:

4 + 10 = 14 
5 + 10 = 15 
2 + 10 = 12 
3 + 10 = 13 
1 + 10 = 11

What am I doing wrong? Why is the same i value used each time? How do I fix this?

Thank you so much for your help!

Dev
  • 921
  • 4
  • 14
  • 31
  • 1
    It is OK, that all tasks run after for cycle end. That is the principle. Regarding the i still the same, try this: insert int temp = i; just before var t = new Task(() => { and use temp inside lambda method instead of i. tasks.ToArray() is useless. IEnumerable is required, what is List too. – Marťas Jul 09 '20 at 19:37
  • Agree with @Martas. Your code will loop and create all the tasks, which are all sleeping at first. The result is that your variable `i` is incremented a bunch of times before the tasks ever actually look at its value. (Hopefully this explains _why_ you see what you see.) – Sean Skelly Jul 09 '20 at 19:40

1 Answers1

0

the value of i is not local to the function, aka the mem allication is the same across so its changed.

for (var i=1; i<=5; i++)
{
    var shouldBe = i + offset;
    var d = new Details();

    var t = new Task<int>(() => {
    var ms = random.Next(1001);    // Choose a wait time between 0 and 1 seconds
    Thread.Sleep(ms);
    d.SleepTime = ms;

    var result = i + offset;
    Console.WriteLine($"{i} + {offset} = {result}  (over {timeout} ms.)");
    return result;
    });

    tasks.Add(t.ExecuteAsync<int>());
    details.Add(d);
}

this should print correctly

for (var i=1; i<=5; i++)
{
    var localCounter = i;
    var shouldBe = localCounter + offset;
    var d = new Details();

    var t = new Task<int>(() => {
        var ms = random.Next(1001);    // Choose a wait time between 0 and 1 seconds
        Thread.Sleep(ms);
        d.SleepTime = ms;

        var result = localCounter + offset;
        Console.WriteLine($"{localCounter} + {offset} = {result}  (over {timeout} ms.)");
        return result;
    });

    tasks.Add(t.ExecuteAsync<int>()); //<- this extention method is just making it more completcated to read. i would remove it.
    details.Add(d);
}

this should help a bucket

static async Task Main(string[] args)
{

    var random = new Random();
    List<Task<Details>> tasks = new List<Task<Details>>();
    for (var i = 1; i <= 20; i++)
    {
        var localCounter = i;

        var t = new Task<Details>(() => {
            var ms = random.Next(1001);
            Task.Delay(ms);
            var result = new Details
            {
                Id = localCounter,
                DelayTime = ms
            };
            Console.WriteLine($"basically done id: {localCounter}");
            return result;
        });

        tasks.Add(t);
    }

    tasks.ForEach(t => t.Start());
    Task.WaitAll(tasks.ToArray());
    foreach (var item in tasks)
    {
        Console.WriteLine($"id: {item.Result.Id}");
        Console.WriteLine($"random delay: {item.Result.DelayTime}");
    }
}
Seabizkit
  • 2,417
  • 2
  • 15
  • 32