“使用”语句与“最终尝试”


81

我有一堆将要使用读/写锁的属性。我可以使用atry finallyusing子句实现它们。

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

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

方法1(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();
        }
    }
}

方法2:

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();
    }
}

1
就像许多答案中已经提到的那样,方法2很好,但是为避免每次使用锁时在堆上造成垃圾,应将ReadLock和WriteLock更改为结构。即使using语句使用结构的IDisposable接口,但C#足够聪明,可以避免装箱!
Michael Gehling

Answers:


94

从MSDN,使用Statement(C#参考)

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();
  }
}

因此,基本上,这是相同的代码,但具有很好的自动空检查和变量的额外作用域。该文档还指出,它“确保IDisposable对象的正确使用”,因此您将来也可能会为任何晦涩的情况获得更好的框架支持。

因此,选择选项2。

将变量包含不再需要的范围内立即结束的作用域也是一个加号。


最好是释放无法在using语句中实例化,无法重用或作为参数传递出去的资源?try / catch!
2012年

2
@bjan好,那为什么还要考虑using呢?那不是using为了什么
chakrit

1
这就是为什么我也提到try/catch它,因为它看起来是处理Viatry/catch/finally块的唯一方法。希望using也能解决这个问题
2012年

好吧,是try/finally的,我猜那是您唯一的选择。IMO,但是,我认为在这种情况下(应该始终调用Dispose()),应该始终有一些对象/代码段负责维护对象的生存期。如果一个类仅处理实例化而其他人必须记住处置它,我觉得那里有点气味。不确定如何在语言级别添加它。
chakrit

我认为避免“使用”的唯一原因是完全避免一次性物品,因为我猜这是另一个对象实例化?
Demetris Leptos

12

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

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

(不过,对这样的单个属性使用读/写锁通常会显得过高。它们最好在更高的级别上使用。这里简单的锁通常就足够了,因为考虑到持有锁的时间,争用的可能性可能很小例如,获取读/写锁定比简单锁定要昂贵得多。


如何保证故障安全?我知道一次try最终将总是触发finally块,有什么办法不会被调用?
杰里米

1
不,对于try / final模式,using语句本质上是语法糖。
布莱尔·康拉德,

使用模型可确保始终丢弃对象。
Nathen Silver

如果您使用反射器,则会看到using块已由编译器转换为try ... finally构造,因此在IL级别上,它们是等效的。“使用”只是糖。
罗伯·沃克,

^^请记住,在某些情况下,最终不会被调用。例如断电。;)
Quibblesome

8

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

一个try没有catch显然应该是一个坏主意; 请参阅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;},您可能会重新考虑该方法。有了更多信息,我相信堆栈溢出可以提供帮助;-)


2
最后尝试并不一定掩盖异常。在我的示例中,获取了锁,然后,如果在范围内引发任何异常,则将释放该锁,但该异常仍会冒泡。
杰里米

1
@Jeremy:如果您的finally块引发异常,它将掩盖在try块中引发的异常-这就是msdn文章所说的使用语法的(相同)问题
Steven A. Lowe

3
它不会掩盖该异常,它将以与您完全相同的方式替换该异常。
乔恩·汉娜

5

我个人尽可能多地使用C#“ using”语句,但是为了避免潜在的问题,我需要做一些具体的事情。为了显示:

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
    }
}

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

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

如果在某种情况下遇到了可能发生重大异常的情况Dispose(),我将针对该情况进行重新设计。坦白说,我怀疑这种情况是否会发生。

同时,“使用”的范围和清理优势使其成为我最喜欢的C#功能之一。顺便说一下,我使用Java,C#和Python作为主要语言,在这里到处都有很多其他语言,而“使用”是我最喜欢的语言功能之一,因为它是实用的日常工作。


提示,提高代码的可读性:除内部“ using”外,不使用花括号来对齐和压缩use语句。
Bent Tranberg '17

4

我喜欢第三种选择

private object _myDateTimeLock = new object();
private DateTime _myDateTime;

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

在您的两个选项中,第二个选项最干净,更容易理解正在发生的事情。


lock语句不能与ReaderWriterLock一样工作。
chakrit

@chakrit:不,但是除非您知道实际上存在锁争用,否则它们可能会更有效。
Michael Burr

没错,但您应该始终先尝试smiple锁。subby的问题没有说明不允许锁定的性能问题或要求。因此,没有理由尝试并保持光滑。只需将其锁定并晃动即可。

4

“属性束”并锁定在属性获取器和设置器级别上看起来是错误的。您的锁定太细粒度了。在最典型的对象用法中,您需要确保获得了一个锁,可以同时访问多个属性。您的具体情况可能有所不同,但我对此表示怀疑。

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


是的,我一定会明白你的意思的。我的大多数属性实际上都是布尔值,整数,我不需要锁定它们,因为它们应该是原子的。有一些日期时间,我想要锁定的字符串。因为少数人需要锁,所以我最好把它放到物业上
杰里米

4

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


1

Try / Catch块通常用于异常处理,而using块用于确保对象被处置。

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

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

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



0

尽管我同意上述许多评论,包括锁的粒度和可疑的异常处理,但问题是方法之一。让我给您一个重要的理由,为什么我更喜欢使用try {}最终模型...抽象。

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

我们处理许多不同的锁定机制和行为。您的锁定对象可能具有特定的超时时间。您的锁实现可能是监视器锁,读取器锁,写入器锁或旋转锁。但是,从调用者的角度来看,所有这些都是无关紧要的,他们关心的是,遵守了锁定资源的约定,并且锁定以与实现方式一致的方式来进行锁定。

interface ILock {
    IDisposable Acquire();
}

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

我喜欢您的模型,但我会考虑对调用者隐藏锁定机制。FWIW,我已经测量了使用技术与最终尝试之间的开销,分配一次性对象的开销将有2-3%的性能开销。


0

我很惊讶没有人建议将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();
        }
    }
}

0

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();
        }
    }
}

0

下面为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();
    }
}

0

实际上,在第一个示例中,为使解决方案具有可比性,您也将在其中实施IDisposable。然后,您Dispose()将从finally块中调用而不是直接释放锁。

然后,您将实现“从苹果到苹果”的实现(和MSIL)(两种解决方案的MSIL都相同)。使用它可能仍然是一个好主意,using因为增加了作用域,并且框架将确保正确使用IDisposable(如果实施IDisposable自己的话,后者的收益会降低)。


-1

傻我 有一种方法可以使每个实例的锁定方法变得更简单(而不是像我之前的文章中的static)。现在我真的很喜欢这样做,因为不需要将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();
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.