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

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

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

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

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

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

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

为了提供范围,您可以使AutoMutex实现IDisposable并使用它如下:

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

在您的IDisposable.Dispose()实现中,释放互斥锁。

情侣点。

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

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

是否滥用IDisposable和“使用”作为获取exception安全的“范围行为”的手段?

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

3)你确定要在抛出某些内容时释放互斥锁吗? 你确定要扔进受保护的区域吗?

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

为什么你首先有一个互斥? 通常因为模式如下:

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

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

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

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

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

  • 国家是一贯但陈旧的
  • 锁定访问状态
  • 使状态不一致
  • 使国家保持一致
  • 如果发生exception,则回滚到陈旧,一致的状态
  • 解锁对州的访问权限
  • 国家现在是一致的,如果没有例外,那就是新的

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

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

 using (Mutex m = new Mutex()) { //Use mutex here } //Mutex is out of scope and disposed 

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

我想每个人都有处理/使用,但这是一个使用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(); }
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(); } 

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 } 

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

对于基于范围的操作,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结构用于执行基于范围的操作。