线程安全和Task.Factory

时间:2014-12-17 12:55:40

标签: c# multithreading task-parallel-library

我是并行编程的新手 我想做一些任务工作 每个任务都以param启动,用id做一些简单的工作 但看起来这些参数都混淆了......

我确定我错过了线程安全中的一些关键因素 你能帮我理解我做错了什么吗?

我不需要任务的任何返回值,我只需要他们完成他们的工作。

static void Main(string[] args)
{
    int NumberOfTasks = 10;

    Task[] tasks = new Task[NumberOfTasks];
    for (int i = 0; i < NumberOfTasks; i++)
    {
       tasks[i] = Task.Factory.StartNew(() => DoSafeWork(i));
    }
    Task.WaitAll(tasks);

    Console.WriteLine("Done !");
    Console.ReadKey();
}

private static void DoSafeWork(int i)
{
    Console.WriteLine("working on Task {0} ", i.ToString());
}

当前输出(可能):

working on Task 3    
working on Task 6    
working on Task 10    
working on Task 10    
working on Task 10    
working on Task 10    
working on Task 10    
working on Task 10    
working on Task 10    
working on Task 10    
Done !

2 个答案:

答案 0 :(得分:2)

问题在于循环变量是在外部循环中定义的。请注意,问题的根源实际上是闭包,与线程无关。

只需像这样创建一个循环变量的本地副本,以便闭包捕获副本:

for (int i = 0; i < NumberOfTasks; i++)
{
    var localCopy = i;
    tasks[i] = Task.Factory.StartNew(() => DoSafeWork(localCopy));
}

有关详细说明,请参阅https://stackoverflow.com/a/512265/219187

答案 1 :(得分:0)

问题,正如Dmi所说,你正在关闭索引,所以当for循环改变它时,所有任务都引用相同的索引。

虽然您可以通过将当前索引值复制到本地变量来解决这个问题,但更优雅的解决方案是使用LINQ:

var tasks = Enumerable.Range(0, NumberOfTasks).Select(i => Task.Factory.StartNew(() => DoSafeWork(i)));
Task.WaitAll(tasks);

此外,如果您不受旧版本.Net的限制,则应使用Task.RunTask.WhenAllawait代替:

var tasks = Enumerable.Range(0, NumberOfTasks).Select(i => Task.Run(() => DoSafeWork(i)));
await Task.WhenAll(tasks);