是否可以在C#中实现作用域锁定?

时间:2008-11-02 11:56:23

标签: c# locking

C ++中的一个常见模式是创建一个包装锁的类 - 在创建对象时隐式获取锁,或者在之后显式获取锁。当对象超出范围时,dtor会自动释放锁定。 是否可以在C#中执行此操作?据我所知,无法保证C#中的dtor何时会在对象超出范围后运行。

澄清: 一般的锁,自旋锁,ReaderWriterLock,等等。 调用Dispose会破坏模式的目的 - 一旦我们退出范围就释放锁 - 无论我们是否在中间调用return,抛出异常或诸如此类的东西。 另外,据我所知,使用仍然只会为GC排队对象,而不是立即销毁它......

6 个答案:

答案 0 :(得分:12)

为了放大Timothy的答案,lock语句确实使用监视器创建了一个范围锁。从本质上讲,这可以转化为这样的东西:

lock(_lockKey)
{
    // Code under lock
}

// is equivalent to this
Monitor.Enter(_lockKey)
try
{
     // Code under lock
}
finally
{
    Monitor.Exit(_lockKey)
}

在C#中,很少使用dtor作为这种模式(请参阅using statement / IDisposable)。您可能在代码中注意到的一件事是,如果Monitor.Enter和try之间发生异步异常,则看起来监视器不会被释放。 JIT实际上特别保证,如果Monitor.Enter紧接在try块之前,则在try块之前不会发生异步异常,从而确保释放。

答案 1 :(得分:5)

您对using的理解不正确,这是一种以确定的方式进行范围操作的方法(不会排队到GC)。

C#提供了提供独占锁定的lock关键字,如果您想要使用不同的类型(例如读/写),则必须使用using语句。

P.S。 This thread可能会让您感兴趣。

答案 2 :(得分:4)

确实你不知道dtor什么时候会运行...但是,如果你实现了IDisposable接口,然后使用'using'块或者自己调用'Dispose()',你将有一个放置代码的地方。

问题:当你说“锁定”时,你的意思是线程锁定,以便一次只有一个线程可以使用该对象吗?如:

lock (_myLockKey) { ... }

请澄清。

答案 3 :(得分:3)

为了完整性,还有另一种方法可以在不使用usingIDisposable的情况下实现类似的RAII效果。在C#using通常更清晰(see also here以获得更多想法),但在其他语言(例如Java)中,或者甚至在C#中如果using由于某种原因不合适,它是有用的知道。

这是一个名为“Execute Around”的成语,其想法是你调用一个方法来执行前后任务(例如锁定/解锁你的线程,或者设置和提交/关闭你的数据库连接等),然后你将一个委托传递给该方法,该委托将实现您希望在其间发生的操作。

e.g:


funkyObj.InOut( delegate{ System.Console.WriteLine( "middle bit" ); } );

根据InOut方法的作用,输出可能类似于:

first bit
middle bit
last bit

正如我所说,这个答案仅仅是为了完整性,using IDisposable以及lock关键字的先前建议将会更好99%时间。

令人遗憾的是,虽然.Net在这方面比其他许多现代OO语言更进一步(我正在看你,Java),但它仍然负责RAII对客户端代码的处理(即使用using)的代码,而在C ++中,析构函数将始终在范围的末尾运行。

答案 4 :(得分:0)

我一直非常为using由开发人员记住要做的事情而烦恼 - 充其量你得到一个警告,大多数人从不费心去宣传错误。所以,我一直在想这样的想法 - 它迫使客户至少尝试做正确的事情。幸运的是,它是一个闭包,所以客户端仍然可以保留资源的副本,并尝试稍后再使用它 - 但这段代码至少试图将客户端推向正确的方向......

public class MyLockedResource : IDisposable
{
    private MyLockedResource()
    {
        Console.WriteLine("initialize");
    }

    public void Dispose()
    {
        Console.WriteLine("dispose");
    }

    public delegate void RAII(MyLockedResource resource);

    static public void Use(RAII raii)
    {
        using (MyLockedResource resource = new MyLockedResource())
        {
            raii(resource);
        }
    }

    public void test()
    {
        Console.WriteLine("test");
    }
}

良好用法:

MyLockedResource.Use(delegate(MyLockedResource resource)
{
    resource.test();
});

用法不好! (不幸的是,这无法阻止......)

MyLockedResource res = null;
MyLockedResource.Use(delegate(MyLockedResource resource)
{
    resource.test();
    res = resource;
    res.test();
});
res.test();

答案 5 :(得分:0)

为什么首先要使用作用域锁定?假设您有以下代码:

lock(obj) {
    ... some logic goes here
}

如果在插入try的{​​{1}}内部发生异常,这通常意味着您现在具有损坏的状态,其他线程将继续在损坏的状态下工作。最好让程序挂起以发出有关该问题的信号。

另一个问题是lock会导致性能下降,但这通常是一个较小的问题。

  

Jeffrey Richter特别建议不要使用try语句。