我需要使用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;
}
析构函数(终结器?)直到很久才被调用。
谷歌还没有指出我正确的方向,但我还在努力......请让我知道你如何解决这个小问题,如果可能的话。
答案 0 :(得分:16)
情侣点。
1)你想要搜索的东西是“一次性模式”。要非常小心地正确实现。当然,Mutex 已经实现了一次性模式,所以我不清楚你为什么想要创建自己的模式,但是,它仍然是很好的学习。
有关使用一次性模式是否明智的一些额外想法,请参阅此问题,就好像它是RAII一样:
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
构造用于执行基于范围的操作。