'使用'声明vs'尝试终于'

时间:2008-11-10 19:31:57

标签: c# .net multithreading using-statement

我有一堆属性,我将使用读/写锁。我可以使用try 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();
    }
}

15 个答案:

答案 0 :(得分:84)

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

所以基本上,它是相同的代码,但有一个很好的自动空值检查和一个额外的变量范围。该文档还指出它“确保正确使用IDisposable对象”,因此您可能会在将来为任何模糊的案例获得更好的框架支持。

所以选择2。

将变量置于范围中,并且在不再需要之后立即结束也是一个优势。

答案 1 :(得分:12)

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

在第一种情况下,有人编辑代码时必须注意不要在Acquire(读|写)锁定调用和try之间插入任何内容。

(对这样的单个属性使用读/写锁定通常是矫枉过正的。它们最好应用在更高的级别。一个简单的锁定通常就足够了,因为争用的可能性可能非常小,因为时间是保持锁定,获取读/写锁定比简单锁定更昂贵。

答案 2 :(得分:8)

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

没有try的{​​{1}}显然应该是一个坏主意;请参阅MSDN了解catch声明同样危险的原因。

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

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

using

简单,一致,干净的解决方案是一个很好的目标,但假设您不能只使用try { try { //Reader-writer lock stuff } finally { //Release lock } } catch(Exception ex) { //Do something with exception } ,您可能会重新考虑这种方法;更多信息我确信Stack Overflow可以提供帮助; - )

答案 3 :(得分: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
    }
}

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

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

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

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

答案 4 :(得分:4)

我喜欢第三个选项

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

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

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

答案 5 :(得分:4)

“束属性”并且在属性getter和setter级别锁定看起来是错误的。你的锁定太精细了。在大多数典型的对象使用中,您需要确保获得锁定以同时访问 more 而不是一个属性。你的具体情况可能有所不同,但我有点怀疑。

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

答案 6 :(得分:3)

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

答案 7 :(得分:1)

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

对于读/写锁定,try / catch可能是最有用的,但你也可以同时使用它们,如下所示:

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

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

答案 8 :(得分:0)

我认为方法2会更好。

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

答案 9 :(得分:0)

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

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

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

interface ILock {
    IDisposable Acquire();
}

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

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

答案 10 :(得分:0)

我很惊讶没有人建议在匿名函数中封装try-finally。就像使用using语句实例化和处理类的技术一样,这可以将锁定保存在一个地方。我更喜欢这个,因为当我考虑释放锁时,我宁愿阅读“最后”这个词而不是“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();
        }
    }
}

答案 11 :(得分: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();
        }
    }
}

答案 12 :(得分:0)

实际上在你的第一个例子中,为了使解决方案具有可比性,你也可以在那里实现“IDisposable”,并从“Finally”块调用“Dispose”而不是直接释放锁。那么你就是“苹果到苹果”的实现(和MSIL)-wise(两种解决方案的MSIL都是一样的)。使用“使用”仍然是一个好主意,因为增加了范围,并且因为框架将确保正确使用“IDisposable”(如果您自己实施“IDisposabe”,后者则不太有用)。

答案 13 :(得分: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();
    }
}

答案 14 :(得分:-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();
        }
    }
}