Home > database >  Method giving back wrong result during Task
Method giving back wrong result during Task

Time:11-24

I have a loop creating three tasks:

List<Task> tasks = new List<Task>();
foreach (DSDevice device in validdevices)
{
    var task = Task.Run(() =>
    {
         var conf = PrepareModasConfig(device, alternativconfig));
         //CHECK-Point1
         string config = ModasDicToConfig(conf);
         //CHECK-Point2
         if (config != null)
         {
             //Do Stuff
         }
         else
         {
             //Do other Stuff
         }
   });
  tasks.Add(task);
 }
 Task.WaitAll(tasks.ToArray());

it calls this method, where some data of a dictionary of a default-config gets overwritten:

private Dictionary<string, Dictionary<string, string>> PrepareModasConfig(DSDevice device, string alternativeconfig)
{
    try
    {
        Dictionary<string, Dictionary<string, string>> config = new Dictionary<string, Dictionary<string, string>>(Project.project.ModasConfig.Config);
        if (config.ContainsKey("[Main]"))
        {
            if (config["[Main]"].ContainsKey("DevName"))
            {
                config["[Main]"]["DevName"] = device.ID;
            }
        }
        return config;
    }
    catch
    {
        return null;
    }
}

and after that, it gets converted into a string with this method:

private string ModasDicToConfig(Dictionary<string, Dictionary<string, string>> dic)
{
    string s = string.Empty;
    try
    {
        foreach (string key in dic.Keys)
        {
            s = s   key   "\n";
            foreach (string k in dic[key].Keys)
            {
                s = s   k   "="   dic[key][k]   "\n";
            }
            s = s   "\n";
        }
        return s;
    }
    catch
    {
        return null;
    }
}

But every Tasks gets the exact same string back.

On //CHECK-Point1 I check the Dic for the changed value: Correct Value for each Task

On //CHECK-Point2 I check the String: Same String on all 3 Tasks (Should be of course different)

Default-Dictionary looks like this: (shortened)

{
  {"[Main]",
     {"DevName", "Default"},
     ...
  },
  ...
}

The resulting string look like that:

[Main]
DevName=003     <--This should be different (from Device.ID)
...

[...]

EDIT: I moved the methods to execute outside the Task. Now I get the correct Results. So I guess it has something to do with the Task?

List<Task> tasks = new List<Task>();
foreach (DSDevice device in validdevices)
{
    var conf = PrepareModasConfig(device, alternativconfig));
    //CHECK-Point1
    string config = ModasDicToConfig(conf);
    //CHECK-Point2
    var task = Task.Run(() =>
    {
         
         if (config != null)
         {
             //Do Stuff
         }
         else
         {
             //Do other Stuff
         }
   });
  tasks.Add(task);
 }
 Task.WaitAll(tasks.ToArray());

CodePudding user response:

The problem isn't caused by tasks. The lambda passed to Task.Run captures the loop variable device so when the tasks are executed, all will use the contents of that variable. The same problem would occur even without tasks as this SO question shows. The following code would print 10 times:

List<Action> actions = new List<Action>();

for (int i = 0; i < 10;   i )
    actions.Add(()=>Console.WriteLine(i));

foreach (Action a in actions)
    a();
------
10
10
10
10
10
10
10
10
10
10

If the question's code used an Action without Task.Run it would still result in bad results.

One way to fix this is to copy the loop variable into a local variable and use only that in the lambda :

for (int i = 0; i < 10;   i )
{
    var ii=i;
    actions.Add(()=>Console.WriteLine(ii));
}

The question's code can be fixed by copying the device loop variable into the loop:

foreach (DSDevice dev in validdevices)
{
    var device=dev;
    var task = Task.Run(() =>
    {
         var conf = PrepareModasConfig(device, alternativconfig));

Another way is to use Parallel.ForEach to process all items in parallel, using all available cores, without creating tasks explicitly:

Parallel.ForEach(validdevices,device=>{
    var conf = PrepareModasConfig(device, alternativconfig));
    string config = ModasDicToConfig(conf);
    ...
});

Parallel.ForEach allows limiting the number of worker tasks through the MaxDegreeOfParallelism option. It's a blocking call because it uses the current thread to process data along with any worker tasks.

  • Related