foreach循环对异步委托的影响-阿里云开发者社区

开发者社区> 技术小甜> 正文

foreach循环对异步委托的影响

简介:
+关注继续查看

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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只是一个语法糖,它对应的代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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可能已经是其它值了。

如果把语法糖改成如下的方式:

1
2
3
4
5
6
7
8
9
   try
    {
      while(e.MoveNext()) 
      
        int m; // INSIDE 
        m = (int)(int)e.Current; 
        funcs.Add(()=>m); 
      }
}

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

因此,一开始的演示代码,只需要如下修改既可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
        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的编译器编译,得到的结果是如预期那样的乱序输出。

















本文转自cnn23711151CTO博客,原文链接: http://blog.51cto.com/cnn237111/1355032,如需转载请自行联系原作者




版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10051 0
css3 transform对其他样式影响,(尤其是position:flixed)
1、transform 会为当前元素添加 position : relative 特性; 当 magin 为负值的时候,就会覆盖到前面的 元素, 然而如果给前面元素添加了transform 属性后,前面的元素就会在上面   2、transform 会限制 position:flxed   当一个元素 有 postion :flxed 属性当时候,应该是相对于文档定位 然而,当父元素中有 transform 属性的时候,该元素的 postion:flxed 属性就会变成 absolute 。
863 0
VS 的编译选项 build下的 platform target -- Any CPU和x86有什么影响?
VS 的编译选项 build下的platform -- Any CPU和x86有什么影响? 现在的问题是,我的一个assembly用Any CPU编译就不能在64位系统下运行了,但是用x86编译就ok   原因是如果用Any CPU编译,那么在64位机器默认的运行就是64位的, 而我的ass...
1030 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13865 0
CentOS7 yum lamp 虚拟主机配置 lamp各组件简单影响性能的参数调整--for 一定的环境需求
LAMP Server on CentOS 7 Updated Tuesday, January 13, 2015 by Joel Kruger This guide provides step-by-step instructions for installing a full-featured LAMP stack on a CentOS 7 system.
847 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
11887 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
7358 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
4496 0
+关注
10146
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载