需要击败GC并且一旦超出范围就将对象销毁

时间:2010-02-04 16:05:06

标签: c# garbage-collection locking

我需要使用Mutex保护几段代码。问题是代码看起来像这样:

lock(mylockobject) {
  if(!foo())
    throw new MyException("foo failed");
  if(!bar())
    throw new MyException("bar failed");
}

使用锁定,它可以按照我的意愿运行,但现在我需要使用互斥锁。这里显而易见的问题是,如果我获取互斥锁并且foo()或bar()失败,我将不得不在抛出每个异常之前明确释放互斥锁。

在C ++中,我将利用在堆栈上创建的对象的范围,并将互斥锁定在对象的构造函数中,然后在析构函数中释放它。使用.NET的垃圾收集,我认为这不会起作用。我写了一个测试应用程序并确认如果我做这样的事情:

public class AutoMutex
{
  private Mutex _mutex;
  public AutoMutex(Mutex mutex)
  {
     _mutex = mutex;
     _mutex.WaitOne();
  }

  ~AutoMutex()
  {
    _mutex.ReleaseMutex();
  }
}

然后有这样的代码:

// some code here...
Mutex my_mutex = new Mutex(false, "MyMutex");
{ // scoping like I would do in C++
  AutoMutex test = new AutoMutex(my_mutex);
  test = null;
}

析构函数(终结器?)直到很久才被调用。

谷歌还没有指出我正确的方向,但我还在努力......请让我知道你如何解决这个小问题,如果可能的话。

8 个答案:

答案 0 :(得分:16)

情侣点。

1)你想要搜索的东西是“一次性模式”。要非常小心地正确实现。当然,Mutex 已经实现了一次性模式,所以我不清楚你为什么想要创建自己的模式,但是,它仍然是很好的学习。

有关使用一次性模式是否明智的一些额外想法,请参阅此问题,就好像它是RAII一样:

Is it abusive to use IDisposable and "using" as a means for getting "scoped behavior" for exception safety?

2)尝试 - 终于也有你想要的语义。当然,“使用”块只是try-finally的语法糖。

3)你是否确定你想在发生什么事情时释放互斥锁?你确定要扔进受保护的区域吗?

由于以下原因,这是一种糟糕的代码味道。

为什么你首先拥有互斥?通常因为模式是这样的:

  • 状态一致但陈旧
  • 锁定对州的访问权
  • 使状态不一致
  • 使状态保持一致
  • 解锁对州的访问
  • 现在状态一致且新鲜

考虑在“使状态保持一致”之前抛出异常时会发生什么。 您解锁对状态的访问权限,该状态现在不一致且陈旧

保持锁定可能是个更好的主意。是的,这意味着冒着死锁的风险,但至少你的程序不是在垃圾,陈旧,不一致的状态下运行。

从一个受锁保护的区域内抛出异常是一个可怕的,可怕的事情,你应该尽可能避免这样做。从锁内部抛出的异常使得您必须在两个可怕的事情之间做出选择:要么遇到死锁,要么在程序操纵不一致状态时遇到疯狂的崩溃和不可重现的行为。

你真正应该实施的模式是:

  • 状态一致但陈旧
  • 锁定对州的访问权
  • 使状态不一致
  • 使状态保持一致
  • 如果发生异常,则回滚到陈旧,一致的状态
  • 解锁对州的访问
  • 状态现在是一致的,如果没有例外,则是新鲜的

这是更安全的替代方案,但编写执行此类交易的代码非常困难。没有人说多线程很容易。

答案 1 :(得分:12)

为了提供范围,您可以制作AutoMutex工具IDisposable并使用它:

using(new AutoMutex(.....))
{
  if(!foo())
    throw new MyException("foo failed");
  if(!bar())
    throw new MyException("bar failed");
}    

IDisposable.Dispose()的实施中,释放互斥锁。

答案 2 :(得分:9)

Mutex实现了IDisposable,因此将其包装在using

using (Mutex m = new Mutex())
{
   //Use mutex here

}
//Mutex is out of scope and disposed

答案 3 :(得分:7)

使用try / finally块或使用IDisposable模式并将您的用法包装在using语句中。

答案 4 :(得分:3)

我认为每个人都有处置/使用,但这是一个使用try / finally的例子:


Mutex m = new Mutex()
m.WaitOne();
try
{
  if(..)
    throw new Exception();              
}
finally
{
  // code in the finally will run regardless of whether and exception is thrown.
  m.ReleaseMutex();
}


答案 5 :(得分:3)

GC不具有确定性。此外,它在调试模式下的行为也不同,这使得在开发环境中处理起来更加困惑。

要按照您希望的方式创建和使用自动互斥锁,请实施IDisposable并使用using关键字在超出范围时销毁/释放AutoMutex。

public class AutoMutex : IDisposable
{
    private Mutex _mutex;  
    public AutoMutex(Mutex mutex)
    {
       _mutex = mutex;
       _mutex.WaitOne();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    void Dispose(bool disposing)
    {
        // method may be called more than once from different threads
        // you should avoid exceptions in the Dispose method
        var victim = _mutex;
        _mutex = null;
        if(victim != null)
        {
          victim.ReleaseMutex();
          victim.Dispose();
        }
        if(disposing)
        {
          // release managed resources here
        }
    }
}

并在使用中

using(new AutoMutex(new Mutex(false, "MyMutex")))
{
  // whatever within the scope of the mutex here
}

答案 6 :(得分:1)

为什么不在try-catch-finally中包围Foo()中的代码,然后让最终释放互斥锁? (如果需要,catch可以重新抛出任何异常,将它们包装在自定义异常类型中。)

答案 7 :(得分:0)

对于基于范围的操作,C#惯用语是try...finally。它看起来像这样:

try {
    acquire the mutex
    do your stuff which may fail with an exception
} finally {
    release the mutex
}

使用对象实例进行资源获取是一种C ++主义,它来自C ++处理生命受当前范围限制的实例的能力。在实例在堆中分配并由垃圾收集器(C#,Java)异步销毁的语言中,析构函数(也称为终结符)不是正确的工具。相反,finally构造用于执行基于范围的操作。