使用 Mutex 同步 C# 对象:在 C# 对象析构函数中调用 ReleaseMutex() 时出现问题

Posted

技术标签:

【中文标题】使用 Mutex 同步 C# 对象:在 C# 对象析构函数中调用 ReleaseMutex() 时出现问题【英文标题】:Use Mutex to synchronize C# object: issue with call to ReleaseMutex() in C# object destructor 【发布时间】:2012-09-21 22:07:57 【问题描述】:

我有一个应用程序代码,我在其中使用互斥锁在创建对象期间同步一些代码。对象构造函数获取互斥体,并且仅在不再需要对象时释放它,因此释放互斥体的一个地方将在对象析构函数中。当我使用应用程序的 2 个实例调试代码时,第一个实例首先获取互斥锁,第二个实例坐下来等待 (mut.WaitOne())。用户然后关闭第一个应用程序实例。在这种情况下,第二个实例 mut.WaitOne() 会抛出异常:“由于放弃的互斥体,等待已完成。”这发生在第一个实例中调用 mut.ReleaseMutex() 之前(我知道是因为它在调用 MutexRelease 之前在对象析构函数代码中命中了我的断点)。似乎在调用 ReleaseMutex() 之前释放了互斥锁,从而导致了异常。我将如何解决这种竞争条件?感谢您的帮助。

public sealed class MyObject

    static ExtDeviceDriver devDrv;
    private Mutex mut = new Mutex(false,myMutex);

    public MyObject()
    
        mut.WaitOne();
        //Thread safe code here.
        devDrv = new ExtDeviceDriver();
    

    ~MyObject()
    
        mut.ReleaseMutex();
    

【问题讨论】:

【参考方案1】:

你的方法有缺陷;这不是您应该(尝试)执行同步的方式。如果你想防止多个应用程序实例......然后这样做,而不是这个。如果您需要同步特定调用,请尽可能在最窄的范围内进行。

我仍然可以在您的方法中创建竞争条件,只需复制原始引​​用并在另一个线程上使用它进行函数调用。除了构造函数之外,实际上没有任何东西是同步的,除了创建一个类的多个实例之外,您并没有阻止任何事情,如果我尝试创建第二个实例,我会遇到死锁。不是很好。

研究IDisposable 模式。终结器不是确定性的。这不是 C++,也不是析构函数,你不能依赖它在你想要的时候执行它。

其次,您的互斥锁应该是静态的。每个实例都有自己的互斥锁,因此您在实例 1 中同步的互斥锁与实例 2 中的互斥锁不同。这需要是共享资源。

来自文档:

废弃的互斥锁通常表示代码中存在严重错误。当线程退出而不释放互斥体时,受互斥体保护的数据结构可能不会处于一致状态。如果可以验证数据结构的完整性,则请求互斥锁所有权的下一个线程可以处理此异常并继续。

对于系统范围的互斥体,废弃的互斥体可能表明应用程序已被突然终止(例如,通过使用 Windows 任务管理器)。

它继续说关于本地 v 系统互斥锁......

互斥体有两种类型:未命名的本地互斥体和命名的系统互斥体。 本地互斥锁仅存在于您的进程中。进程中的任何线程都可以使用它,该线程引用了表示互斥体的 Mutex 对象。每个未命名的 Mutex 对象代表一个单独的本地互斥体。

听起来你想要一个系统互斥锁。告诉我们哪些调用需要同步,以便我们向您展示如何做如何?这是一个非常基本的例子:

class Foo

    static Mutex _mut(false);
    public MyObject()
    
        _mut.WaitOne();
        //Thread safe code here.
        devDrv = new ExtDeviceDriver();
        _mut.ReleaseMutex();
    

    public void SomeSynchronizedMethod()
    
        // synchronize this call
        _mut.WaitOne();
        devDrv.DoSomething();
        _mut.ReleaseMutex();
    

【讨论】:

命名互斥锁是系统互斥锁。我的代码对象是创建一个新对象并仅调用一次“devDrv = new ExtDeviceDriver()”并阻止其他实例调用它,直到该实例终止。如果不是互斥锁,还有什么其他选择? @Kevin:互斥锁不是这里的问题,你的设计才是。如果您的应用程序要求在系统级别仅实例化一个接口,那么您应该有一个单实例应用程序,而不是像这样奇怪的东西。我无法确切告诉您应该做什么,因为您没有告诉我们您要解决什么问题,您只是向我们展示了您提出的解决方案(这不起作用)并要求我们解决它。 @Kevin: google.com/… @Kevin:其他方法可能是简单地尝试连接到设备并在它不起作用时返回失败。再说一次,我很乐意提供解决方案,但我需要你从高层次上告诉我问题出在哪里。 该应用程序旨在运行多个实例。外部 DLL 调用 ExtDeviceDriver 不提供检查是否已建立连接的调用,因此我的实现是解决这两个限制。每个应用程序都会为它尝试通信的每个外部设备创建一个对象。我所能做的就是阻止来自第二个实例的访问,一旦它被前一个实例调用,就不会创建 ExtDeviceDriver。否则,第二个实例将在该调用中挂起。【参考方案2】:

析构函数不是确定性的; CLR 不保证它们何时运行。相反,在你的类中实现IDisposable,并强制调用者在using(...) 块中使用它的实例。这将确保您的 Dispose 实现在需要时被调用。

例如,

public sealed class MyObject : IDisposable

    static ExtDeviceDriver devDrv;
    private Mutex mut = new Mutex(false,myMutex);

    public MyObject()
    
        mut.WaitOne();
        //Thread safe code here.
        devDrv = new ExtDeviceDriver();
    

    public void Dispose() 
        mut.ReleaseMutex();
    


然后调用者只需要这样做:

using (var x = new MyObject()) 
    // etc

当执行流程存在using 块时,Dispose 将被调用,而不管异常或其他任何情况。

【讨论】:

以上是关于使用 Mutex 同步 C# 对象:在 C# 对象析构函数中调用 ReleaseMutex() 时出现问题的主要内容,如果未能解决你的问题,请参考以下文章

C#多线程学习 互斥对象

C#多线程间的同步问题

面试常考:C#用两个线程交替打印1-100的五种方法

C#多线程:深入了解线程同步lock,Monitor,Mutex,同步事件和等待句柄(中)

C#各种同步方法 lock, Monitor,Mutex, Semaphore, Interlocked, ReaderWriterLock,AutoResetEvent, ManualResetEv

c#编写的软件如何多开