Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

For Loop result in Overflow with Task.Run or Task.Start

Tags:

c#

task

got a Problem, hope someone can help me out.

i try to start 4 Task in an Loop but im getting an ArgumentOutOfRangeException:

 for (int i = 0; i < 4; i++)
     {
          //start task with current connection
          tasks[i] = Task<byte[]>.Run(() => GetData(i, plcPool[i]));
     }

The Loop gets an Overflow because i = 4

if i start the Tasks without a Loop, they run without any Problems:

            tasks[0] = Task<byte[]>.Run(() => GetData(0, plcPool[0]));
            tasks[1] = Task<byte[]>.Run(() => GetData(1, plcPool[1]));
            tasks[2] = Task<byte[]>.Run(() => GetData(2, plcPool[2]));
            tasks[3] = Task<byte[]>.Run(() => GetData(3, plcPool[3]));

dont know why? The Tasks GetData from a Siemens PLC via Socket Connection. The PLC Supports up to 32 Connections. I receive 200 Bytes per Connection.

 private byte[] GetData(int id, PLC plc)
    {
        switch (id)
        {
            case 0:
                return plc.ReadBytes(DataType.DataBlock, 50, 0, 200);
            case 1:
                return plc.ReadBytes(DataType.DataBlock, 50, 200, 200);
            case 2:
                return plc.ReadBytes(DataType.DataBlock, 50, 500, 200);
            case 3:
                return plc.ReadBytes(DataType.DataBlock, 50, 700, 200);
            case 4:
                return plc.ReadBytes(DataType.DataBlock, 50, 900, 117);
            default:
                return null;
        }
    }

any idea?

Regards Sam

like image 672
Sam Avatar asked Oct 22 '15 07:10

Sam


1 Answers

It's probably caused by a closure problem.

Try this:

 for (int i = 0; i < 4; i++)
 {
      //start task with current connection
      int index = i;
      tasks[index] = Task<byte[]>.Run(() => GetData(index, plcPool[index]));
 }

What is probably happening is that when the last thread starts running, the loop has already incremented i to 4, and that's the value that gets passed to GetData(). Capturing the value of i into a separate variable index and using that instead should solve that issue.

As an example, if you try this code:

public static void Main()
{
    Console.WriteLine("Starting.");

    for (int i = 0; i < 4; ++i)
        Task.Run(() => Console.WriteLine(i));

    Console.WriteLine("Finished. Press <ENTER> to exit.");
    Console.ReadLine();
}

it will often give you this kind of output:

Starting.
Finished. Press <ENTER> to exit.
4
4
4
4

Change that code to:

public static void Main()
{
    Console.WriteLine("Starting.");

    for (int i = 0; i < 4; ++i)
    {
        int j = i;
        Task.Run(() => Console.WriteLine(j));
    }

    Console.WriteLine("Finished. Press <ENTER> to exit.");
    Console.ReadLine();
}

and you get something like

Starting.
Finished. Press <ENTER> to exit.
0
1
3
2

Note how it is STILL NOT NECESSARILY IN ORDER! You will see all the correct values printed out, but in an indeterminate order. Multithreading is tricksy!

like image 132
Matthew Watson Avatar answered Sep 21 '22 12:09

Matthew Watson