题
在以下两个片段,是第一个安全的或必须执行第二个?
通过安全我的意思是保证在其中该线程被创建的相同循环迭代呼吁富方法每个线程?
,或者必须引用复制到一个新的变量“本地”的每次循环?
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
-
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Foo f2 = f;
Thread thread = new Thread(() => f2.DoSomething());
threads.Add(thread);
thread.Start();
}
更新:作为乔恩斯基特的答复中指出,这并没有什么专门做与线程
解决方案
编辑:这一切变化在C#5,具有改变到变量被定义,其中(在编译器的眼睛)。从的 C#5开始,它们是相同的强>
<强> C#5之前强>
第二是安全的;第一是不
通过foreach
,变量声明的外强>环 - 即
Foo f;
while(iterator.MoveNext())
{
f = iterator.Current;
// do something with f
}
这意味着,只有1在封闭范围方面f
和线程可能会很可能会感到困惑 - 呼吁某些情况下,该方法多次,而不是在所有的别人。可以用第二可变声明 内解决这个循环:
foreach(Foo f in ...) {
Foo tmp = f;
// do something with tmp
}
这于是具有在每个封闭范围单独tmp
,所以没有这个问题的风险。
这里的问题的一个简单的证明:
static void Main()
{
int[] data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
foreach (int i in data)
{
new Thread(() => Console.WriteLine(i)).Start();
}
Console.ReadLine();
}
输出(任意):
1
3
4
4
5
7
7
8
9
9
添加临时变量和它的工作原理:
foreach (int i in data)
{
int j = i;
new Thread(() => Console.WriteLine(j)).Start();
}
(每个数字一次,但当然的顺序不保证)
其他提示
流行克特林和Marc Gravell的答案是正确的。所有我想补充的是,以我有关闭的文章(其中谈到Java的两个链接和C#)。只是认为这可能会增加一点的值。
编辑:我认为这是值得给它没有穿线的不可预知性的例子。这里的展示了这两种方法很短,但完整的程序。 “坏行为”列表打印出10个十倍; “好动作”列表计数从0到9。
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
List<Action> badActions = new List<Action>();
List<Action> goodActions = new List<Action>();
for (int i=0; i < 10; i++)
{
int copy = i;
badActions.Add(() => Console.WriteLine(i));
goodActions.Add(() => Console.WriteLine(copy));
}
Console.WriteLine("Bad actions:");
foreach (Action action in badActions)
{
action();
}
Console.WriteLine("Good actions:");
foreach (Action action in goodActions)
{
action();
}
}
}
您需要使用选项2,创建围绕改变可变的封闭件将使用该变量的值时,所使用的变量,而不是在闭合创建时间。
编辑:要清楚,在C#闭包是“词汇关闭”,这意味着他们没有抓住一个变量的值,但变量本身。这意味着,创建一个封闭于变化的变量当闭合实际上是对变量的引用不是它的一个副本的值。
EDIT2:添加链接到所有博客文章,如果有人有兴趣阅读有关编译器内部
这是一个有趣的问题,似乎就像我们已经看到的人在所有不同的方式回答。我的印象是,第二种方式是唯一安全的方式下。我掀起一个真正的快速证明:
class Foo
{
private int _id;
public Foo(int id)
{
_id = id;
}
public void DoSomething()
{
Console.WriteLine(string.Format("Thread: {0} Id: {1}", Thread.CurrentThread.ManagedThreadId, this._id));
}
}
class Program
{
static void Main(string[] args)
{
var ListOfFoo = new List<Foo>();
ListOfFoo.Add(new Foo(1));
ListOfFoo.Add(new Foo(2));
ListOfFoo.Add(new Foo(3));
ListOfFoo.Add(new Foo(4));
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
}
}
如果你运行这个,你会看到选项1是definetly也不安全。
在你的情况下,可以由您ListOfFoo
映射到线程的序列避免该问题,而不使用复制特技:
var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
t.Start();
}
两者都是安全的,因为的C#5版(.NET框架4.5)。详情请参阅此问题:拥有的foreach的使用变量C#被改变5?
Foo f2 = f;
指向同一基准
f
所以没有丢失,焉得虎子......