【问题标题】:'using' statement vs 'try finally'“使用”语句与“最终尝试”
【发布时间】:2010-09-21 16:33:28
【问题描述】:

我有一堆属性,我将在这些属性上使用读/写锁。我可以使用try finallyusing 子句来实现它们。

try finally 中,我将在try 之前获取锁,并在finally 中释放。在using 子句中,我将创建一个类,该类在其构造函数中获取锁,并在其 Dispose 方法中释放。

我在很多地方都使用读/写锁,所以我一直在寻找可能比try finally 更简洁的方法。我很想听听一些关于为什么一种方法可能不被推荐,或者为什么一种方法可能比另一种更好的想法。

方法一(try finally):

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        rwlMyLock_m .AcquireReaderLock(0);
        try
        {
            return dtMyDateTime_m
        }
        finally
        {
            rwlMyLock_m .ReleaseReaderLock();
        }
    }
    set
    {
        rwlMyLock_m .AcquireWriterLock(0);
        try
        {
            dtMyDateTime_m = value;
        }
        finally
        {
            rwlMyLock_m .ReleaseWriterLock();
        }
    }
}

方法二:

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        using (new ReadLock(rwlMyLock_m))
        {
            return dtMyDateTime_m;
        }
    }
    set
    {
        using (new WriteLock(rwlMyLock_m))
        {
            dtMyDateTime_m = value;
        }
    }
}

public class ReadLock : IDisposable
{
    private ReaderWriterLock rwl;
    public ReadLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireReaderLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseReaderLock();
    }
}

public class WriteLock : IDisposable
{
    private ReaderWriterLock rwl;
    public WriteLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireWriterLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseWriterLock();
    }
}

【问题讨论】:

  • 就像许多答案中已经说过的那样,方法2非常好,但是为了避免每次使用锁时堆上的垃圾,您应该将ReadLock和WriteLock更改为结构。即使 using 语句使用结构的 IDisposable 接口,C# 也足够聪明,可以避免装箱!

标签: c# .net multithreading using-statement


【解决方案1】:

我绝对更喜欢第二种方法。它在使用时更简洁,更不容易出错。

在第一种情况下,编辑代码的人必须小心不要在 Acquire(Read|Write)Lock 调用和 try 之间插入任何内容。

(不过,在像这样的单个属性上使用读/写锁通常是过大的。它们最好应用在更高的级别。在这里,一个简单的锁通常就足够了,因为考虑到时间的争用可能性可能非常小锁被持有,并且获取读/写锁是比简单锁更昂贵的操作。

【讨论】:

  • 故障安全怎么样?我知道 try finally 总是会触发 finally 块,有什么方法不会调用 dispose 吗?
  • 不,using 语句本质上是 try/finally 模式的语法糖。
  • using 模型确保对象总是被释放。
  • 如果您使用反射器,您将看到编译器将 using 块转换为 try ... finally 构造,因此在 IL 级别它们是等价的。 “使用”只是语法糖。
  • ^^ 请记住,在某些情况下可能不会调用 finally。以停电为例。 ;)
【解决方案2】:

我认为方法2会更好。

  • 属性中的代码更简单、更易读。
  • 由于锁定代码不必多次重写,因此不易出错。

【讨论】:

    【解决方案3】:

    DRY 说:第二种解决方案。第一个解决方案重复了使用锁的逻辑,而第二个没有。

    【讨论】:

      【解决方案4】:

      来自 MSDN,using Statement (C# Reference)

      using 语句可确保调用 Dispose,即使在您调用对象上的方法时发生异常也是如此。您可以通过将对象放在 try 块中,然后在 finally 块中调用 Dispose 来获得相同的结果;事实上,这就是编译器翻译 using 语句的方式。前面的代码示例在编译时扩展为以下代码(注意额外的花括号以创建对象的有限范围):

      {
        Font font1 = new Font("Arial", 10.0f);
        try
        {
          byte charset = font1.GdiCharSet;
        }
        finally
        {
          if (font1 != null)
            ((IDisposable)font1).Dispose();
        }
      }
      

      所以基本上,它是相同的代码,但有一个很好的自动 null 检查和一个额外的变量范围。该文档还声明它“确保正确使用 IDisposable 对象”,因此您最好在将来为任何晦涩的情况提供更好的框架支持。

      所以选择选项 2。

      将变量放在一个范围内在不再需要后立即结束也是一个优点。

      【讨论】:

      • 如何释放不能在 using 语句中实例化或被重用或作为输出参数传递的资源?try/catch!!!
      • @bjan 好吧,在这种情况下你为什么还要考虑using?这不是using 的用途。
      • 这就是为什么我还提到try/catch,因为它看起来是通过try/catch/finally 块处理的唯一方法。希望using 也能处理这个问题
      • 嗯,是的,try/finally 在这种情况下我猜是你唯一的选择。不过,IMO,我认为在这种情况下应该总是有一些对象/代码片段负责维护对象的生命周期(应该始终调用 Dispose()。)如果一个类只处理实例化而其他人必须记住处理它,我觉得那里有点味道。不确定如何在语言级别添加它。
      • 我认为避免“使用”的唯一原因是完全避免一次性用品,因为我猜它是另一个对象实例化?
      【解决方案5】:

      Try/Catch 块一般用于异常处理,而 using 块用于确保对象被释放。

      对于读/写锁,try/catch 可能是最有用的,但您也可以同时使用两者,如下所示:

      using (obj)
      {
        try { }
        catch { }
      }
      

      这样您就可以隐式调用您的 IDisposable 接口并简化异常处理。

      【讨论】:

        【解决方案6】:

        我喜欢第三个选项

        private object _myDateTimeLock = new object();
        private DateTime _myDateTime;
        
        public DateTime MyDateTime{
          get{
            lock(_myDateTimeLock){return _myDateTime;}
          }
          set{
            lock(_myDateTimeLock){_myDateTime = value;}
          }
        }
        

        在您的两个选项中,第二个选项最简洁,也更容易理解发生了什么。

        【讨论】:

        • lock 语句的工作方式与 ReaderWriterLock 不同。
        • @chakrit:不,但除非您知道确实存在锁争用,否则它们可能会更高效。
        • 没错,但您应该始终先使用微笑锁。 subby 的问题没有说明任何关于性能问题或不允许锁定的要求。因此,没有理由尝试变得圆滑。只需锁定它并摇动它。
        【解决方案7】:

        考虑两种解决方案都不好的可能性,因为它们掩盖了异常

        没有catchtry 显然是个坏主意;请参阅 MSDN 了解为什么 using 语句同样危险。

        另请注意,Microsoft 现在建议使用 ReaderWriterLockSlim 而不是 ReaderWriterLock。

        最后,请注意 Microsoft 示例使用 两个 try-catch 块来避免这些问题,例如

        try
        {
            try
            {
                 //Reader-writer lock stuff
            }
            finally
            {
                 //Release lock
            }
         }
         catch(Exception ex)
         {
            //Do something with exception
         }
        

        一个简单、一致、干净的解决方案是一个很好的目标,但假设您不能只使用lock(this){return mydateetc;},您可能会重新考虑该方法;有更多信息,我相信 StackOverflow 可以提供帮助 ;-)

        【讨论】:

        • 尝试最终不一定会掩盖异常。在我的示例中,获取了锁,然后如果在范围内抛出任何异常,锁将被释放,但异常仍然会冒泡。
        • @Jeremy:如果您的 finally 块抛出异常,它将掩盖您的 try 块中抛出的异常 - 这就是 msdn 文章所说的使用语法的(相同)问题
        • 它不会掩盖异常,它会以与您完全相同的方式替换异常。
        【解决方案8】:

        我个人尽可能频繁地使用 C#“使用”语句,但我会同时执行一些特定的操作以避免提到的潜在问题。举例说明:

        void doSomething()
        {
            using (CustomResource aResource = new CustomResource())
            {
                using (CustomThingy aThingy = new CustomThingy(aResource))
                {
                    doSomething(aThingy);
                }
            }
        }
        
        void doSomething(CustomThingy theThingy)
        {
            try
            {
                // play with theThingy, which might result in exceptions
            }
            catch (SomeException aException)
            {
                // resolve aException somehow
            }
        }
        

        请注意,我将“using”语句分隔为一个方法,并将对象的使用分隔为另一个带有“try”/“catch”块的方法。我可能会为相关对象嵌套几个这样的“使用”语句(我有时会在生产代码中深入三到四个)。

        在这些自定义IDisposable 类的Dispose() 方法中,我捕获异常(但不是错误)并记录它们(使用Log4net)。我从未遇到过任何这些异常都可能影响我的处理的情况。像往常一样,允许潜在的错误向上传播调用堆栈,并且通常会终止处理并记录适当的消息(错误和堆栈跟踪)。

        如果我在Dispose() 期间遇到了可能发生重大异常的情况,我会针对这种情况重新设计。坦率地说,我怀疑这永远不会发生。

        同时,“使用”的范围和清理优势使其成为我最喜欢的 C# 功能之一。顺便说一句,我使用 Java、C# 和 Python 作为我的主要语言,还有很多其他的语言,“使用”是我最喜欢的语言功能之一,因为它是一种实用的日常主力.

        【讨论】:

        • 提示,为了代码的可读性:对齐和压缩uses语句,除了内部的“using”之外没有花括号。
        【解决方案9】:

        “一堆属性”和在属性 getter 和 setter 级别的锁定看起来是错误的。您的锁定过于细粒度。在最典型的对象用法中,您需要确保获得了一个锁以同时访问更多 个属性。你的具体情况可能会有所不同,但我有点怀疑。

        无论如何,当您访问对象而不是属性时获取锁将大大减少您必须编写的锁定代码量。

        【讨论】:

        • 是的,我绝对明白你的意思。我的大部分属性实际上是布尔值、整数,我不需要锁定它们,因为它们应该是原子的。有一些日期时间,我想要锁定的字符串。因为少数人需要锁,我最好把它放在财产上
        【解决方案10】:

        虽然我同意上述许多 cmets,包括锁定的粒度和可疑的异常处理,但问题在于方法之一。让我给你一个重要原因,为什么我更喜欢使用 try {} finally 模型...抽象。

        我有一个与您的模型非常相似的模型,但有一个例外。我定义了一个基本接口 ILock,并在其中提供了一个名为 Acquire() 的方法。 Acquire() 方法返回 IDisposable 对象,因此意味着只要我正在处理的对象是 ILock 类型,它就可以用于执行锁定范围。为什么这很重要?

        我们处理许多不同的锁定机制和行为。您的锁定对象可能具有特定的超时时间。您的锁实现可能是监视器锁、读取器锁、写入器锁或自旋锁。但是,从调用者的角度来看,所有这些都是无关紧要的,他们关心的是锁定资源的合同是否得到履行,以及锁定以与其实现一致的方式执行。

        interface ILock {
            IDisposable Acquire();
        }
        
        class MonitorLock : ILock {
            IDisposable Acquire() { ... acquire the lock for real ... }
        }
        

        我喜欢你的模型,但我会考虑对调用者隐藏锁定机制。 FWIW,我测量了 using 技术与 try-finally 的开销,分配一次性对象的开销将有 2-3% 的性能开销。

        【讨论】:

          【解决方案11】:

          我很惊讶没有人建议将 try-finally 封装在匿名函数中。就像使用 using 语句实例化和处理类的技术一样,这将锁定保持在一个位置。我自己更喜欢这个,只是因为当我考虑释放锁时,我宁愿读“finally”这个词而不是“Dispose”这个词。

          class StackOTest
          {
              private delegate DateTime ReadLockMethod();
              private delegate void WriteLockMethod();
          
              static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
              private DateTime dtMyDateTime_m;
              public DateTime MyDateTime
              {
                  get
                  {
                      return ReadLockedMethod(
                          rwlMyLock_m,
                          delegate () { return dtMyDateTime_m; }
                      );
                  }
                  set
                  {
                      WriteLockedMethod(
                          rwlMyLock_m,
                          delegate () { dtMyDateTime_m = value; }
                      );
                  }
              }
          
              private static DateTime ReadLockedMethod(
                  ReaderWriterLock rwl,
                  ReadLockMethod method
              )
              {
                  rwl.AcquireReaderLock(0);
                  try
                  {
                      return method();
                  }
                  finally
                  {
                      rwl.ReleaseReaderLock();
                  }
              }
          
              private static void WriteLockedMethod(
                  ReaderWriterLock rwl,
                  WriteLockMethod method
              )
              {
                  rwl.AcquireWriterLock(0);
                  try
                  {
                      method();
                  }
                  finally
                  {
                      rwl.ReleaseWriterLock();
                  }
              }
          }
          

          【讨论】:

            【解决方案12】:

            我傻了。有一种方法可以使锁定方法成为每个实例的一部分(而不是像我之前的帖子中那样的静态方法),从而使事情变得更简单。现在我真的更喜欢这个,因为不需要将 `rwlMyLock_m' 传递给其他类或方法。

            class StackOTest
            {
                private delegate DateTime ReadLockMethod();
                private delegate void WriteLockMethod();
            
                static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
                private DateTime dtMyDateTime_m;
                public DateTime MyDateTime
                {
                    get
                    {
                        return ReadLockedMethod(
                            delegate () { return dtMyDateTime_m; }
                        );
                    }
                    set
                    {
                        WriteLockedMethod(
                            delegate () { dtMyDateTime_m = value; }
                        );
                    }
                }
            
                private DateTime ReadLockedMethod(ReadLockMethod method)
                {
                    rwlMyLock_m.AcquireReaderLock(0);
                    try
                    {
                        return method();
                    }
                    finally
                    {
                        rwlMyLock_m.ReleaseReaderLock();
                    }
                }
            
                private void WriteLockedMethod(WriteLockMethod method)
                {
                    rwlMyLock_m.AcquireWriterLock(0);
                    try
                    {
                        method();
                    }
                    finally
                    {
                        rwlMyLock_m.ReleaseWriterLock();
                    }
                }
            }
            

            【讨论】:

              【解决方案13】:

              SoftwareJedi,我没有帐户,所以我无法编辑我的答案。

              无论如何,以前的版本并不适合通用用途,因为读锁总是需要返回值。这解决了:

              class StackOTest
              {
                  static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
                  private DateTime dtMyDateTime_m;
                  public DateTime MyDateTime
                  {
                      get
                      {
                          DateTime retval = default(DateTime);
                          ReadLockedMethod(
                              delegate () { retval = dtMyDateTime_m; }
                          );
                          return retval;
                      }
                      set
                      {
                          WriteLockedMethod(
                              delegate () { dtMyDateTime_m = value; }
                          );
                      }
                  }
              
                  private void ReadLockedMethod(Action method)
                  {
                      rwlMyLock_m.AcquireReaderLock(0);
                      try
                      {
                          method();
                      }
                      finally
                      {
                          rwlMyLock_m.ReleaseReaderLock();
                      }
                  }
              
                  private void WriteLockedMethod(Action method)
                  {
                      rwlMyLock_m.AcquireWriterLock(0);
                      try
                      {
                          method();
                      }
                      finally
                      {
                          rwlMyLock_m.ReleaseWriterLock();
                      }
                  }
              }
              

              【讨论】:

                【解决方案14】:

                实际上,在您的第一个示例中,为了使解决方案具有可比性,您还可以在那里实现IDisposable。然后你会从finally 块中调用Dispose(),而不是直接释放锁。

                那么您将是“苹果对苹果”的实施(和 MSIL)(两种解决方案的 MSIL 将是相同的)。使用using 可能仍然是一个好主意,因为它增加了范围,并且因为框架将确保正确使用IDisposable(如果您自己实现IDisposable,后者的好处不大)。

                【讨论】:

                  【解决方案15】:

                  以下为 ReaderWriterLockSlim 类创建扩展方法,允许您执行以下操作:

                  var rwlock = new ReaderWriterLockSlim();
                  using (var l = rwlock.ReadLock())
                  {
                       // read data
                  }
                  using (var l = rwlock.WriteLock())
                  {
                      // write data
                  }
                  

                  代码如下:

                  static class ReaderWriterLockExtensions() {
                      /// <summary>
                      /// Allows you to enter and exit a read lock with a using statement
                      /// </summary>
                      /// <param name="readerWriterLockSlim">The lock</param>
                      /// <returns>A new object that will ExitReadLock on dispose</returns>
                      public static OnDispose ReadLock(this ReaderWriterLockSlim readerWriterLockSlim)
                      {
                          // Enter the read lock
                          readerWriterLockSlim.EnterReadLock();
                          // Setup the ExitReadLock to be called at the end of the using block
                          return new OnDispose(() => readerWriterLockSlim.ExitReadLock());
                      }
                      /// <summary>
                      /// Allows you to enter and exit a write lock with a using statement
                      /// </summary>
                      /// <param name="readerWriterLockSlim">The lock</param>
                      /// <returns>A new object that will ExitWriteLock on dispose</returns>
                      public static OnDispose WriteLock(this ReaderWriterLockSlim rwlock)
                      {
                          // Enter the write lock
                          rwlock.EnterWriteLock();
                          // Setup the ExitWriteLock to be called at the end of the using block
                          return new OnDispose(() => rwlock.ExitWriteLock());
                      }
                  }
                  
                  /// <summary>
                  /// Calls the finished action on dispose.  For use with a using statement.
                  /// </summary>
                  public class OnDispose : IDisposable
                  {
                      Action _finished;
                  
                      public OnDispose(Action finished) 
                      {
                          _finished = finished;
                      }
                  
                      public void Dispose()
                      {
                          _finished();
                      }
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 2016-04-15
                    • 2021-10-02
                    • 2014-01-09
                    • 2016-03-05
                    • 1970-01-01
                    • 2013-01-27
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多