views:

131

answers:

4

The following code creates the correct number of files, but every file contains the contents of the first list. Can anyone spot what I've done wrong please?

private IList<List<string>> GetLists()
{
  // Code omitted for brevity...
}

private void DoSomethingInParallel()
{
  var lists = GetLists();

  var tasks = new List<Task>();

  var factory = new TaskFactory();

  foreach (var list in lists)
  {
    tasks.Add(factory.StartNew(() =>
    {
      WriteListToLogFile(list);
    }));
  }

  Task.WaitAll(tasks.ToArray());
}
A: 

I am not sure why you have a list for "tasks", you are only ever using one of them.

edit: factory.StartNew Creates and starts a System.Threading.Tasks.Task!!

Thinking out loud: so there is a separate task for each of the List<String> in its list which calls WriteListToLogFile?

I think you will need to use

ThreadPool.QueueUserWorkItem 

in your code after task.Add

look at this example (see the accepted answer post) link

VoodooChild
You haven't read the code correctly. factory.StartNew(...) creates and starts a new Task for each item in the lists collection.
Chris Arnold
+1 ok I see my mistake now :) thanks!! The var declaration got me confused.
VoodooChild
ThreadPool is the old way of doing parallel programming and doesn't give as much feedback and control as the new Parallel Task Library.
Chris Arnold
A: 

Ran into this same problem myself. I'm still not sure why it happens, but I was able to get it to work properly by passing in a state object

 foreach (var list in lists) 
  { 
    tasks.Add(factory.StartNew((o) => 
    { 
      var l = o as List<string>;
      WriteListToLogFile(l); 
    }, list)); 
  } 
Jacob Adams
You shouldn't have to use a state object. See my answer/explanation.
Ade Miller
+1  A: 

Apologies for not replying to this earlier. I found a solution - although I don't understand why it works...

Originally, I had this ...

foreach (var list in lists)
  {
    tasks.Add(factory.StartNew(() =>
    {
      WriteListToLogFile(list);
    }));
  }

Changing the sequential foreach to a parallel foreach fixes the problem...

Parallel.ForEach<string>(lists, list =>
    tasks.Add(factory.StartNew(() =>
    {
      WriteListToLogFile(list);
    }));
  );
Chris Arnold
You really shouldn't have to use Parallel.ForEach instead of foreach. This will be very inefficient for doing some thing as lightweight as starting tasks. See my answer for an explanation.
Ade Miller
+1  A: 

The reason why is down to the way C# evaluates anonymous methods, they're not true closures. It really has nothing to do with the TPL. The following code prints out all d's. This is not what yoy would expect

List<Task> tasks = new List<Task>();
List<string> lists = new List<string>();
lists.AddRange(new string[] { "a", "b", "c", "d" });

foreach (var list in lists)
{
    tasks.Add(Task.Factory.StartNew(() =>
    {
        Console.WriteLine(list);
    }));
} 

The reason is because the value of list when the anonymous method was created is not the one that gets evaluated in the method body. The value of list at the time the method was executed is used. You can force a fix for this by doing the following:

List<Task> tasks = new List<Task>();
List<string> lists = new List<string>();
lists.AddRange(new string[] { "a", "b", "c", "d" });

foreach (var list in lists)
{
    var localList = list;  
    tasks.Add(Task.Factory.StartNew(() =>
    {
        Console.WriteLine(localList);
    }));
} 

You don't have to pass in the list value to the anonymous method explicitly.

This blog post goes into this in much more detail:

http://blogs.msdn.com/b/abhinaba/archive/2005/10/18/482180.aspx

Ade Miller