C#警惕匿名方法造成的变量共享实例分析

2019-12-26 14:11:13于丽

值得一提的是,在实际情况下以上三点理论都皆可能不满足。在某些特别简单的情况下(例如匿名方法中完全不涉及局部变量和其他方法),编译器只会简单生成一个静态的方法来构造一个委托实例,因为这样可以获得更好的性能。

对于之前的案例,我们现在也将它进行一番改写,这样便可“避免”使用匿名对象,也可以清楚地展现出问题原因:

 

 
  1. private class AutoGeneratedClass  { 
  2. public List<Item> m_batchItems;  public void WaitCallback(object o) 
  3. {  DataContext db = new DataContext(); 
  4. db.Items.InsertAllOnSubmit(this.m_batchItems);  db.SubmitChanges(); 
  5. }  } 
  6. static void Process()  {  
  7. var helper = new AutoGeneratedClass();  helper.m_batchItems = new List<Item>(); 
  8. foreach (var item in ...)  { 
  9. helper.m_batchItems.Add(item);  if (helper.m_batchItems.Count > 1000) 
  10. {  ThreadPool.QueueUserWorkItem(helper.WaitCallback); 
  11. helper.m_batchItems = new List<Item>();  } 
  12. }  } 

编译器会自动生成一个AutoGeneratedClass类,并且在Process方法中使用这个类的实例来代替原来的batchItems局部变量。同样,交给ThreadPool的委托对象也从匿名方法变成了AutoGeneratedClass实例的公有方法。因此线程池每次调用的便是该实例的WaitCallback方法。

现在问题应该一目了然了吧?每次把委托交给线程池之后,线程池并不会立即执行,而会保留到合适的时间再进行。而WaitCallback方法在执行时,它会读取m_batchItems这个Field字段“当前”所引用的对象。而与此同时,Process方法已经“抛弃”了原本我们要提交的数据,因此会引起提交到数据库中数据的丢失。同时,在准备每批次数据的过程中,很有可能会发起两次数据提交,两个线程提交同样一批Item时,就抛出了所谓“莫名其妙”的异常。