'foreach' failing when using Parallel Task Library
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());
}
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:
Link
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);
}));
);
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
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));
}
精彩评论