【问题标题】:c# foreach with locked propertyc# foreach 锁定属性
【发布时间】:2012-01-08 00:20:07
【问题描述】:

好的,这就是我拥有的财产

public List<String> names{
    get{
        lock(_names)
            return _names;
       }
   set{
       lock(_names)
           _names = value
      }
}
private List<String> _names;

现在说我对这样的名称进行 foreach

foreach(String s in names)
{
    Console.WriteLine(s);
}

我的问题是,names 是锁定整个foreach,还是每次设置s 时只锁定lock,然后在foreach 内解锁。

如果这令人困惑,说我尝试这样做

foreach(String s in names)
{
    lock(names)
        Console.WriteLine(s);
}

我会陷入僵局吗?

【问题讨论】:

    标签: c# foreach locking


    【解决方案1】:
    lock(_names) return _names;
    

    是:

    try
    {
      Monitor.Enter(_names);
      return _names;
    }
    finally
    {
      Monitor.Exit(_names);
    }
    

    相当于:

    Monitor.Enter(_names);
    Monitor.Exit(_names);
    return _names;
    

    以这种方式看待它应该更清楚地表明锁定是不必要的。 您可能有兴趣使用thread-safe collections

    之一

    【讨论】:

    • 严格来说并非如此。其实和try { Monitor.Enter(_names); return _names; } finally { Monitor.Exit(_names); }是一样的
    • 其实我觉得不太对,_names会在Monitor.Exit执行前被拷贝到一个临时的,否则退出后返回前可能会发生一些改变_names。
    • 对,因为 temp var 除了在退出后返回之外没有其他用途,我的简化版本是准确的。感谢您的反对:\
    • kelton52,我在回复@Joachim Isaksson 时假设他误解了我的回答而对我投了反对票。无论如何,为了清楚起见,我对其进行了编辑,以防其他人认为我的回答是错误的,或者实际上对这次讨论产生了负面影响。
    • ...当我对某件事投反对票时,我总是会投赞成票,后来得到澄清。给你。
    【解决方案2】:

    在这种情况下,您的锁实际上什么都不做。 getter 大致展开如下

    public List<string> names {
      get {
        Monitor.Enter(_names); 
        try {
          return _names;
        } finally {
          Monitor.Exit(_names);
        }
      }
    }
    

    这意味着对资源的访问仅在读取引用时同步。它不会保护List&lt;string&gt; 的完整性。

    names 引用仅在 foreach 中读取一次,因此在 body 期间不会被锁定。它不会导致任何死锁,但也不会起到太多保护作用

    【讨论】:

    • 我不会说它什么都不做,我可以有效地说,当我得到名字时,我抓住它时列表不会改变。
    • @kelton52 参考读取在 .Net 中已经是原子性的。使用锁来保护属性读/写不会在数据完整性或同步方面为您提供任何帮助
    • 所以你是说它模棱两可,或者这是一种不好的做事方式?
    • @kelton52 我主要是说它在您没有锁定的情况下没有提供明显的价值。 .net 中的锁通常用于在共享资源上进行同步,以提供数据完整性保证。在这里,锁唯一保护的是存储在字段中的引用。这已经具有原子读/写保证,因此没有什么可以防止损坏
    • @kelton52 => 尽管存在关于锁的问题,但返回支持字段并不会阻止调用者对拥有类不知道的 List 进行更改。您可能会考虑让您的 getter 返回 ReadOnlyCollection
    【解决方案3】:

    它没有锁定在foreach 中。它被锁定一次,到get 一个引用,然后迭代。 (此外,属性中的 lock 语句目前是不必要的;这些操作是原子的。)

    最有可能的是,您应该锁定在循环体之外,以及修改集合的位置。


    响应您的编辑,不。这不是并行的foreach,因此循环不可能自行卡住,更不用说死锁了。再一次,这只会浪费处理能力。

    【讨论】:

    • 因为我的代码是一个例子,它与天气无关,或者代码是否有用。
    • @kelton52:我认为你的反对票是不公平的。您的代码是否是示例无关紧要。 minitech 只是为您提供有关您在问题中提供的代码的反馈。这是很好的反馈。
    • 好的,所以锁定非线程代码没有任何目的可能并不明显。
    【解决方案4】:

    在属性的_get方法中只锁定了一个:

        void Main()
        {
            Test test = new Test();
            test.CallMe();
        }
    
    
        public class Test {
    
            List<string> _names= new List<string>(); 
    
            public List<string> Names {
                get {
                Console.WriteLine("Lock");
                lock(_names) {
                    Console.WriteLine("Exit");
                    return _names;
                }
    
                }       
    
            }
    
            public void CallMe()
            {
                foreach(String s in Names)
                {
                    Console.WriteLine(s);
                }
            }
     }
    

    如果是则输出

    Lock
    Exit
    

    IL 代码清楚地表明锁定发生在属性的_get 方法内:

    IL_0000:  newobj      UserQuery+Test..ctor
    IL_0005:  stloc.0     
    IL_0006:  ldloc.0     
    IL_0007:  callvirt    UserQuery+Test.CallMe
    
    Test.get_Names:        //GET METHOD OF THE PROPERTY
    IL_0000:  ldstr       "Lock"
    IL_0005:  call        System.Console.WriteLine
    IL_000A:  ldc.i4.0    
    IL_000B:  stloc.0     
    IL_000C:  ldarg.0     
    IL_000D:  ldfld       UserQuery+Test._names
    IL_0012:  dup         
    IL_0013:  stloc.2     
    IL_0014:  ldloca.s    00 
    IL_0016:  call        System.Threading.Monitor.Enter  //LOCK
    IL_001B:  ldstr       "Exit"
    IL_0020:  call        System.Console.WriteLine
    IL_0025:  ldarg.0     
    IL_0026:  ldfld       UserQuery+Test._names
    IL_002B:  stloc.1     
    IL_002C:  leave.s     IL_0038
    IL_002E:  ldloc.0     
    IL_002F:  brfalse.s   IL_0037
    IL_0031:  ldloc.2     
    IL_0032:  call        System.Threading.Monitor.Exit //UNLOCK
    IL_0037:  endfinally  
    IL_0038:  ldloc.1     
    IL_0039:  ret         
    
    Test.CallMe:     // CALLME METHOD CODE
    IL_0000:  ldarg.0     
    IL_0001:  call        UserQuery+Test.get_Names //ONCE !!
    IL_0006:  callvirt    System.Collections.Generic.List<System.String>.GetEnumerator
    IL_000B:  stloc.1     
    IL_000C:  br.s        IL_001C
    IL_000E:  ldloca.s    01 
    IL_0010:  call        System.Collections.Generic.List<System.String>.get_Current
    IL_0015:  stloc.0     
    IL_0016:  ldloc.0     
    IL_0017:  call        System.Console.WriteLine
    IL_001C:  ldloca.s    01 
    IL_001E:  call        System.Collections.Generic.List<System.String>.MoveNext
    IL_0023:  brtrue.s    IL_000E
    IL_0025:  leave.s     IL_0035
    IL_0027:  ldloca.s    01 
    IL_0029:  constrained. System.Collections.Generic.List<>.Enumerator
    IL_002F:  callvirt    System.IDisposable.Dispose
    IL_0034:  endfinally  
    IL_0035:  ret         
    
    Test..ctor:
    IL_0000:  ldarg.0     
    IL_0001:  newobj      System.Collections.Generic.List<System.String>..ctor
    IL_0006:  stfld       UserQuery+Test._names
    IL_000B:  ldarg.0     
    IL_000C:  call        System.Object..ctor
    IL_0011:  ret         
    

    【讨论】:

      【解决方案5】:

      get_Names 方法只会被调用一次,并且在项的迭代过程中锁会结束(会有no锁)。这可能不是您想要的,您可能应该使用更精细的锁,例如:

      lock (someLockObject)
      {
          foreach(String s in names)
          {
              Console.WriteLine(s);
          }
      }
      

      当你在循环中加锁时,你会一个接一个地获得许多锁,这几乎肯定不是你想要的,因为操作 foreach 循环将不再是原子的。

      【讨论】:

      • 如果我这样做names.ForEach也是这样吗?
      • @kelton52:是的。想想会发生什么: 1. 你调用get_names 方法。 2. 它启动一个锁。 3. 将返回值放入堆栈。 4.结束锁。 5. 方法返回。 6. 对该返回值(锁外)调用ForEach() 方法。
      • @kelton52:使用 LINQ,结果是一样的。让我们明确一点:这样做并没有同步列表
      • @kelton52:lock(_names) 不会阻止其他人使用_nameslock 关键字使用传入的对象作为 key,当其他线程调用 lock 时,它将确保没有其他线程锁定该键(如果是,它将等待直到另一个线程完成他的锁定)。您可以毫无问题地在同一个线程中访问和更改_names,因为_names 并未真正锁定。
      • @kelton52:让我们再次澄清一下:不。不正确。它们都不受保护。 您没有通过锁定您的属性来同步列表。您没有同步任何尚未同步的内容。
      【解决方案6】:

      foreach 循环会短暂锁定 _names 以获取 List,但会在开始实际循环之前解锁。

      锁定 _names 可能不是很好的形式,我会引入另一个对象来锁定,它不会被假定锁定的实际设置操作更改。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-09
        • 1970-01-01
        • 1970-01-01
        • 2012-05-07
        相关资源
        最近更新 更多