85

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

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

15 回答 15

97

来自 MSDN,使用语句(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。

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

于 2008-11-10T19:43:02.067 回答
12

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

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

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

于 2008-11-10T19:36:22.797 回答
9

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

Atry没有 acatch显然是个坏主意;请参阅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;},您可能会重新考虑该方法;有更多信息,我确信 Stack Overflow 可以提供帮助;-)

于 2008-11-10T19:52:04.730 回答
5

我个人尽可能频繁地使用 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
    }
}

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

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

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

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

于 2008-11-10T20:40:18.143 回答
4

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

于 2008-11-10T19:42:06.450 回答
4

我喜欢第三个选项

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

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

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

于 2008-11-10T19:43:34.280 回答
4

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

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

于 2008-11-10T21:32:14.310 回答
1

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

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

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

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

于 2008-11-10T19:43:13.220 回答
1

下面为 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();
    }
}
于 2013-09-09T23:03:27.667 回答
0

我认为方法2会更好。

  • 属性中更简单、更易读的代码。
  • 由于锁定代码不必多次重写,因此不易出错。
于 2008-11-10T19:41:04.767 回答
0

虽然我同意上述许多评论,包括锁定的粒度和可疑的异常处理,但问题是方法之一。让我给你一个重要的理由,为什么我更喜欢使用 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% 的性能开销。

于 2008-11-11T05:04:19.203 回答
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();
        }
    }
}
于 2009-05-31T19:22:57.027 回答
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();
        }
    }
}
于 2009-05-31T19:54:03.297 回答
0

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

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

于 2012-03-26T03:49:42.030 回答
-1

傻我。有一种方法可以使锁定方法成为每个实例的一部分(而不是像我之前的帖子中那样的静态方法),从而使事情变得更简单。现在我真的更喜欢这个,因为没有必要将 `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();
        }
    }
}
于 2009-05-31T19:29:41.133 回答