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,如需转载请自行联系原作者




相关文章
|
SQL 存储 测试技术
企业销售管理系统的设计与实现(论文+源码)_kaic
企业销售管理系统的设计与实现(论文+源码)_kaic
|
负载均衡 网络协议 算法
OSPF与其他IGP协议的比较:全面解析与应用场景
OSPF与其他IGP协议的比较:全面解析与应用场景
443 0
|
Linux 开发工具 Android开发
Flutter之搭建环境
Flutter之搭建环境
|
数据安全/隐私保护 Python Windows
三种方法,Python轻松提取PDF中全部图片
三种方法,Python轻松提取PDF中全部图片
531 3
|
机器学习/深度学习 人工智能 自然语言处理
PVG:用小模型验证大模型输出,解决“黑盒”难题
【8月更文挑战第4天】随AI技术的发展,机器学习系统广泛应用,但在高风险领域如医疗和金融中,其决策需可验证与解释。为此,提出了“Prover-Verifier Games”(PVG)框架,通过两个学习者——证明者与验证者的博弈,前者提供决策及证据,后者评估证据真伪并做决策,以此提升决策透明度。实验显示,在图像分类和自然语言推理任务中,验证者能有效区分真假证据,即便证明者提供虚假信息。不过,PVG也面临计算成本高和适用范围有限等问题。
427 1
|
Python
python编写一个坦克大战
【7月更文挑战第6天】
569 6
|
存储 关系型数据库 数据库
关系型数据库结构化数据存储
【5月更文挑战第10天】
496 7
|
人工智能
AI 排查流水线问题
AI 排查流水线问题
287 2
|
存储 API 索引
LabVIEW利用 IMAQdx 驱动通过编程的方式导入或导出相机的配置文件
LabVIEW利用 IMAQdx 驱动通过编程的方式导入或导出相机的配置文件
297 0
|
机器学习/深度学习 搜索推荐 算法
Scikit-Learn在推荐系统中的应用
【4月更文挑战第17天】本文探讨了如何使用Scikit-learn构建推荐系统,包括基于内容、协同过滤和混合推荐。Scikit-learn提供TF-IDF向量化器和特征选择工具用于内容推荐,K-Means和余弦相似性实现协同过滤。虽然缺乏专门的推荐系统算法和大规模数据处理能力,但其丰富的算法库、易用性和社区支持使其在推荐系统领域仍有应用价值。结合其他工具,Scikit-learn可作为构建推荐系统的重要组件。