C# foreach标识符和闭包
在以下两个代码段中,第一个是安全的还是必须执行第二个 通过safe,我的意思是每个线程是否保证从创建线程的同一循环迭代中调用Foo上的方法 还是必须将对新变量“local”的引用复制到循环的每个迭代中C# foreach标识符和闭包,c#,enumeration,closures,C#,Enumeration,Closures,在以下两个代码段中,第一个是安全的还是必须执行第二个 通过safe,我的意思是每个线程是否保证从创建线程的同一循环迭代中调用Foo上的方法 还是必须将对新变量“local”的引用复制到循环的每个迭代中 var threads = new List<Thread>(); foreach (Foo f in ListOfFoo) { Thread thread = new Thread(() => f.DoSomething()); threads.Ad
var threads = new List<Thread>();
foreach (Foo f in ListOfFoo)
{
Thread thread = new Thread(() => f.DoSomething());
threads.Add(thread);
thread.Start();
}
var threads=newlist();
foreach(列表中的Foo f)
{
线程线程=新线程(()=>f.DoSomething());
线程。添加(线程);
thread.Start();
}
-
var threads=newlist();
foreach(列表中的Foo f)
{
Foo f2=f;
线程线程=新线程(()=>f2.DoSomething());
线程。添加(线程);
thread.Start();
}
更新:正如Jon Skeet在回答中指出的,这与线程无关
Foo f2 = f;
指向与相同的参考
f
因此,没有损失,也没有收获…您需要使用选项2,在使用变量时,而不是在创建闭包时,围绕变化的变量创建闭包,将使用变量的值 编辑:为了清楚起见,在C#中,闭包是“词汇闭包”,这意味着它们不捕获变量的值,而是捕获变量本身。这意味着,当为一个不断变化的变量创建闭包时,闭包实际上是对该变量的引用,而不是其值的副本
Edit2:如果有人有兴趣阅读有关编译器内部的文章,可以添加到所有博客文章的链接。Edit:这将更改C#5中的所有内容,并更改变量的定义位置(在编译器眼中)。从C#5开始,它们是相同的
在C#5之前 二是安全;第一个不是 使用
foreach
,变量在循环外部声明为,即
Foo f;
while(iterator.MoveNext())
{
f = iterator.Current;
// do something with f
}
这意味着就闭包范围而言,只有1f
,线程很可能会感到困惑——在某些实例上多次调用该方法,而在其他实例上则完全没有。您可以使用循环中的第二个变量声明来解决此问题:
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();
}
输出(随机):
添加一个临时变量,它就可以工作了:
foreach (int i in data)
{
int j = i;
new Thread(() => Console.WriteLine(j)).Start();
}
(每个数字一次,但顺序当然不能保证)Pop Catalin和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();
}
}
}
使用系统;
使用System.Collections.Generic;
课堂测试
{
静态void Main()
{
List badActions=new List();
List goodActions=新列表();
对于(int i=0;i<10;i++)
{
int copy=i;
添加(()=>Console.WriteLine(i));
添加(()=>Console.WriteLine(复制));
}
Console.WriteLine(“错误操作:”);
foreach(不良行为中的行为)
{
动作();
}
Console.WriteLine(“好动作:”);
foreach(goodActions中的Action)
{
动作();
}
}
}
这是一个有趣的问题,我们似乎看到人们以各种方式回答。我的印象是,第二条路是唯一安全的路。我快速地做了一个证明:
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();
}
}
}
class-Foo
{
私人内部id;
公共Foo(内部id)
{
_id=id;
}
公共无效剂量测定法()
{
WriteLine(string.Format(“Thread:{0}Id:{1}”,Thread.CurrentThread.ManagedThreadId,this.\u Id));
}
}
班级计划
{
静态void Main(字符串[]参数)
{
var listofoo=新列表();
添加(新的Foo(1));
添加(新的Foo(2));
添加(新的Foo(3));
添加(新的Foo(4));
var threads=newlist();
foreach(列表中的Foo f)
{
线程线程=新线程(()=>f.DoSomething());
线程。添加(线程);
thread.Start();
}
}
}
如果运行此选项,您将看到选项1绝对不安全。在您的情况下,您可以通过将
listofoo
映射到一系列线程来避免问题,而无需使用复制技巧:
var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
t.Start();
}
从C#version 5(.NET framework 4.5)开始,这两个版本都是安全的。查看此问题了解详细信息:这不是魔术。它只是捕获环境。这里和for循环的问题是捕获变量发生了变异(重新分配)。leppie:编译器为您生成代码,通常不容易看出这到底是什么代码。这是编译器魔法的定义,如果有的话。@leppie:我和康拉德在一起。编译器的长度让人觉得很神奇,尽管语义定义得很清楚,但它们并没有被很好地理解。有一句老话说,任何不被很好理解的东西都可以与魔法相提并论吗?@Jon Skeet你的意思是“任何足够先进的技术都无法与魔法区分开来”:)它没有指向一个参考。这是一个参考。它指向同一个对象,但它是一个不同的引用。我认为这适用于值和引用类型。谢谢-我附加了这个问题,说它不是关于线程的。它也出现在你网站视频中的一个对话中是的,我似乎记得我在那里使用了线程版本,其中一个反馈建议是避免线程——使用上面的例子更清楚。很高兴知道视频正在被观看:)甚至理解
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();
}
}
}
var threads = ListOfFoo.Select(foo => new Thread(() => foo.DoSomething()));
foreach (var t in threads)
{
t.Start();
}