foreach循环对异步委托的影响

在使用foreach对异步委托赋值的时候,发现一个问题。代码以下:c#

static void Main(string[] args)
 {
     List<Task> lst_tsk = new List<Task>();
     List<int> lst_item = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
     foreach (var item in lst_item)
     {
         Task tsk = new Task(() =>
         {
             Console.WriteLine(item);
         });
         lst_tsk.Add(tsk);
         tsk.Start();
     }
     Console.ReadLine();
}

往Task中,赋值一个拉姆达表达式,期待运行的结果应该是1,2,3,4,5,6,7,8,9,10乱序输出。可是实际上的结果是10,10,10,10,10,10,10,10,10,10。不少人都认为这是c#编译器的一个bug。Eric作出了解释,根据Eric的文章,在foreach循环语句中的变量只有一个item,该变量在循环事后,被赋值为10了。当异步线程启动的时候,取到的item早就变成10了,所以就得出上面的结果。闭包

根据Eric的文章,foreach只是一个语法糖,它对应的代码以下异步

IEnumerator<int> e = ((IEnumerable<int>)values).GetEnumerator();
try
{
  int m; // OUTSIDE THE ACTUAL LOOP
  while(e.MoveNext())
  {
 m = (int)(int)e.Current;
 funcs.Add(()=>m);
  }
}
finally
{
  if (e != null) ((IDisposable)e).Dispose();
}

能够看到m并不包括在while语句中,并且()=>m的意思是返回当前m变量的值,而不是返回委托建立时m变量的值。所以当这个委托真正运行的时候,找到的m可能已是其它值了。ide

若是把语法糖改为以下的方式:oop

   try 
    {
      while(e.MoveNext())  
      {  
        int m; // INSIDE  
        m = (int)(int)e.Current;  
        funcs.Add(()=>m);  
      }
}

那么m在while内部,每个m都是单独的。根据Eric,不这样改的一个缘由就是,它可能会增长了在循环中使用闭包的次数,(由于异步线程在启动时,都会用到循环中的m,这个m的生命周期在while循环中,只能经过闭包机制,使得其值可以继续保留在内存中,可以让异步委托在调用的时候继续访问到该值)。并且,若是这样修改了,用户会以为foreach每个循环都使用了一个新的变量,而不是一个存储了新值的旧变量。线程

所以,一开始的演示代码,只须要以下修改既能够了:blog

        static void Main(string[] args)
        {
            List<Task> lst_tsk = new List<Task>();
            List<int> lst_item = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            foreach (var item in lst_item)
            {
var copy= item;//增长一个临时的拷贝变量
                Task tsk = new Task(() =>
                {
                    Console.WriteLine(copy );
                });
                lst_tsk.Add(tsk);
                tsk.Start();
            }
            Console.ReadLine();
        }

这样的话,每次委托运行的时候,都会去找copy 变量了。生命周期

多是不少人的意见影响了C#编译器团队,在C#5.0中,他们决定修改这个问题,foreach循环中的变量存在于循环中,所以每次循环都使用的是一个新的变量。for循环暂时不作修正。所以,演示代码在VS2012下,使用C#5.0的编译器编译,获得的结果是如预期那样的乱序输出。ip

相关文章
相关标签/搜索