C#中如何发现Mutex被获取了?

发布于 2024-09-05 06:12:05 字数 1373 浏览 12 评论 0原文

如何从 C# 中的互斥体句柄中找到已获取互斥体?

mutex.WaitOne(timeout) 超时时,它返回 false。但是,我如何从互斥体句柄中找到它? (也许使用 p/invoke。)

更新

public class InterProcessLock : IDisposable
{
    readonly Mutex mutex;

    public bool IsAcquired { get; private set; }

    public InterProcessLock(string name, TimeSpan timeout)
    {
        bool created;
        var security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
        mutex = new Mutex(false, name, out created, security);
        IsAcquired = mutex.WaitOne(timeout);
    }

    #region IDisposable Members

    public void Dispose()
    {
        if (IsAcquired)
        {
            mutex.ReleaseMutex();
            IsAcquired = false;
        }
    }

    #endregion
}

目前,我正在使用自己的属性 IsAcquired 来确定是否应该释放互斥体。不是必需的,但更清楚的是,不使用 IsAcquired 属性表示的信息的辅助副本,而是直接询问互斥体是否由我获取。因为如果我没有获取它,调用 mutex.ReleaseMutex() 会抛出异常。

(通过获得状态,我的意思是当我拥有互斥体时,互斥体处于未发出信号状态。)

(编辑:我已添加IsAcquired = false; 感谢 mattdekrey 的帖子。)

How can I find from mutex handle in C# that a mutex is acquired?

When mutex.WaitOne(timeout) timeouts, it returns false. However, how can I find that from the mutex handle? (Maybe using p/invoke.)

UPDATE:

public class InterProcessLock : IDisposable
{
    readonly Mutex mutex;

    public bool IsAcquired { get; private set; }

    public InterProcessLock(string name, TimeSpan timeout)
    {
        bool created;
        var security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
        mutex = new Mutex(false, name, out created, security);
        IsAcquired = mutex.WaitOne(timeout);
    }

    #region IDisposable Members

    public void Dispose()
    {
        if (IsAcquired)
        {
            mutex.ReleaseMutex();
            IsAcquired = false;
        }
    }

    #endregion
}

Currently, I am using my own property IsAcquired to determine whether I should release a mutex. Not essential but clearer, would be not to use a secondary copy of the information represented by IsAcquired property, but rather to ask directly the mutex whether it is acquired by me. Since calling mutex.ReleaseMutex() throws an exception if it is not acquired by me.

(By acquired state I mean that the mutex is in not-signaled state when I am owning the mutex.)

(EDIT: I have added IsAcquired = false; thanks to mattdekrey's post.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

甜点 2024-09-12 06:12:05

之所以没有干净的方法来做到这一点,是因为这不是一个好主意,而且原因是,当您依赖这种类型的逻辑时,竞争条件很容易引入。所以你的设计需要改变。

首先,您不应该在构造函数中获取锁。将此类转换为返回正确初始化的互斥对象的工厂。这样你就可以知道你是否获得了锁。

不要依赖 Dispose 来释放锁,这会导致死锁缠身的代码难以维护。使用 try/finally 块来确保它被释放。

超时有点粗略。只有在未获取锁时才使用超时才被视为正常操作。无法获取锁通常是一个错误,而仅仅通过超时来避免它会隐藏该错误。如果您需要超时,请考虑使用事件(可能是 AutoResetEvent),这可能更合适。

The reason there is no clean way to do this is because it is not a good idea and the reason is because race conditions are -very- easy to introduce when you rely on this type of logic. So your design needs to change.

First, you should not acquire a lock in a constructor. Turn this class into a factory that returns a properly initialized mutex object. That way you can know if you acquired the lock or not.

DO NOT rely on Dispose to release locks, this is asking for deadlock ridden code that is hard to maintain. Use a try/finally block to ensure it is released.

Timeouts are a bit sketchy. Only use timeouts when not acquiring the lock would be considered normal operation. Not being able to acquire the lock is usually a bug and merely avoiding it with timeouts hides the bug. If you need timeouts, consider using an event (maybe AutoResetEvent), this may be more appropriate.

暖风昔人 2024-09-12 06:12:05

您可能会发现,Mutex 类上没有公共成员:
http://msdn.microsoft.com/en-us/library /system.threading.mutex_members.aspx

也没有公共本机函数:
http://msdn.microsoft.com/en -us/library/ms686360%28v=VS.85%29.aspx

但是,有一些未记录/不受支持的函数,特别是在 ntdll.dll 中。这些允许访问系统对象。但是,这些功能可能会在未来版本的操作系统中发生变化或不可用。

所以,答案是:使用常规手段是不可能的。

As you may found, there are no public members on Mutex class:
http://msdn.microsoft.com/en-us/library/system.threading.mutex_members.aspx

There is also no public native functions for that:
http://msdn.microsoft.com/en-us/library/ms686360%28v=VS.85%29.aspx

However, there are some undocumented/unsupported functions especially in ntdll.dll. These allow accessing system objects. However, these functions may change or not be available in future versions of operating system.

So, the answer is: It is not possible using conventional means.

感性不性感 2024-09-12 06:12:05

好吧,这并不完全是您所要求的,但我认为它可以解决您的问题:为什么不专门针对互斥体被其他人获取时发生的异常添加一些错误处理呢?

public void Dispose()
{
    if (IsAcquired)
        try
        { mutex.ReleaseMutex(); }
        catch (System.Threading.SynchronizationLockException)
        {
            // Handle the exception, assuming you need to do anything.
            // All other exceptions would still be passed up the stack.
        }
}

Well, it's not exactly what you're asking for, but I think it would solve your problem: why not just add some error handling specifically for the exception that occurs if the Mutex is aquired by someone else?

public void Dispose()
{
    if (IsAcquired)
        try
        { mutex.ReleaseMutex(); }
        catch (System.Threading.SynchronizationLockException)
        {
            // Handle the exception, assuming you need to do anything.
            // All other exceptions would still be passed up the stack.
        }
}
や莫失莫忘 2024-09-12 06:12:05

为什么你不能使用 Mutex.OpenExisting

try
{
    Mutex foundMutex = Mutex.OpenExisting("MyTestingMutex");

    // Found Mutex
    foundMutex.ReleaseMutex();
}
catch (System.Threading.WaitHandleCannotBeOpenedException)
{
    //   System.Threading.WaitHandleCannotBeOpenedException:
    //     The named mutex does not exist.
}

编辑

我猜测其中一些。

看起来你正在尝试开发一个 API。您在 API 中提供的项目之一是 InterProcessLock。

我假设您正在跨线程共享一个集合,并且您正在使用互斥体来确保一次只有一个操作。

using (InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)))
{
    if(myLock.IsAcquired)
    {
        // I have control then I can delete, add to the collection.
    }
}

我会重新考虑这个设计。如果我从未将 InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)) 包装在 using 中怎么办?不会调用 Dispose。如果用户根本不调用 Dispose 会怎样?

将会有一个废弃的互斥锁

来自 MSDN

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

如果您试图保护您的用户,您可能希望通过控制他们的互斥体来帮助他们,这样他们就不必担心它。

一个可能的例子是

public static bool PerformLockedProcess(Action process, string commonLockName, TimeSpan timeout)
{
    Mutex mutex = null;

    // Get the Mutex for the User
    try
    {
        bool created;
        var security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));

        mutex = new Mutex(false, commonLockName, out created, security);

        bool acquired = mutex.WaitOne(timeout);

        if (acquired)
        {
            process();

            return true;
        }

        return false;
    }
    finally
    {
        // Make sure we do not abandon the Mutex
        if (mutex != null)
        {
            try
            {
                mutex.ReleaseMutex();
            }
            catch (ApplicationException)
            {
                // In case that failes
            }
        }
    }
}

这是一种可能的方式。这完全取决于目标是什么。我不会依赖最终用户来调用 Dispose,因为互斥锁是一种操作系统构造。如果名称不明确,它可能会影响使用相同互斥体名称的其他进程。

Why can't you use Mutex.OpenExisting

try
{
    Mutex foundMutex = Mutex.OpenExisting("MyTestingMutex");

    // Found Mutex
    foundMutex.ReleaseMutex();
}
catch (System.Threading.WaitHandleCannotBeOpenedException)
{
    //   System.Threading.WaitHandleCannotBeOpenedException:
    //     The named mutex does not exist.
}

EDIT

I am guessing some of this.

It seems like you are trying to develop an API. One of the items you are offering in your API is an InterProcessLock.

I am going to assume you are sharing a collection across threads and you are using the Mutex to make sure only one operation is on it a time.

using (InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)))
{
    if(myLock.IsAcquired)
    {
        // I have control then I can delete, add to the collection.
    }
}

I would reconsider this design. What if I never wraped InterProcessLock myLock = new InterProcessLock("LockMutex", TimeSpan.FromMilliseconds(100.0)) in a using? Dispose would not be called. What if the user never calls the Dispose at all?

There would be an abandoned Mutex

From MSDN

Caution
An abandoned mutex often indicates a serious error in the code. When a thread exits without releasing the mutex, the data structures protected by the mutex might not be in a consistent state. The next thread to request ownership of the mutex can handle this exception and proceed, if the integrity of the data structures can be verified.

If you are trying to protect your users you might want to help them by controlling the Mutex for them so they never have to worry about it.

A possible example is

public static bool PerformLockedProcess(Action process, string commonLockName, TimeSpan timeout)
{
    Mutex mutex = null;

    // Get the Mutex for the User
    try
    {
        bool created;
        var security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));

        mutex = new Mutex(false, commonLockName, out created, security);

        bool acquired = mutex.WaitOne(timeout);

        if (acquired)
        {
            process();

            return true;
        }

        return false;
    }
    finally
    {
        // Make sure we do not abandon the Mutex
        if (mutex != null)
        {
            try
            {
                mutex.ReleaseMutex();
            }
            catch (ApplicationException)
            {
                // In case that failes
            }
        }
    }
}

This is one possible way. It all depends on what the goal is. I would NOT relay on the final user to call a Dispose since a Mutex is an operating system construct. And if the name is not unquie it could effect other processes using the same mutex name.

鸠书 2024-09-12 06:12:05

这不会有利于问题的原始发布者,但就这样。

虽然我并不反对其他关于正确使用互斥锁的发帖者的观点,但我有一个应用程序,我需要测试某人是否拥有互斥锁,而无需我自己取得所有权。正如其他人提到的,唯一的方法是使用来自 ntdll.dll 的未记录的 NtQueryMutant 系统调用。我为 Mutex 类创建了一个扩展方法,可以像这样使用:

        bool createdNew = true;
        var m = new Mutex(false, MutexName, out createdNew);
        if ( m != null)
        {
            int currentCount;
            bool ownedByCaller, abandonedState;
            if (m.TryQuery(out currentCount, out ownedByCaller, out abandonedState))
            {
                Console.WriteLine(string.Format("Created New: {3}, Count: {0}, OwvedByMe: {1}, Abandoned: {2}",
                    currentCount, ownedByCaller, abandonedState, createdNew));
            }
            m.Close();
        }

这是实现

public static class MutexExtensionMethods
{
    public static bool TryQuery(this Mutex m, out int currentCount, out bool ownedByCaller, out bool abandonedState)
    {
        currentCount = -1;
        ownedByCaller = abandonedState = false;
        try
        {
            var handle = m.SafeWaitHandle;
            if (handle != null)
            {
                var h = handle.DangerousGetHandle();
                MutantBasicInformation mbi;
                int retLength;
                var ntStatus = NtQueryMutant(
                    h,
                    MutantInformationClass.MutantBasicInformation,
                    out mbi, 
                    Marshal.SizeOf(typeof(MutantBasicInformation)),
                    out retLength);
                GC.KeepAlive(handle); // Prevent "handle" from being collected before NtQueryMutant returns
                if (ntStatus == 0)
                {
                    currentCount   = mbi.CurrentCount;
                    ownedByCaller  = mbi.OwnedByCaller;
                    abandonedState = mbi.AbandonedState;
                    return true;
                }
            }
        }
        catch
        {
        }
        return false;
    }

    #region NTDLL.DLL

    [DllImport("ntdll.dll")]
    public static extern uint NtQueryMutant(
        [In] IntPtr MutantHandle,
        [In] MutantInformationClass MutantInformationClass,
        [Out] out MutantBasicInformation MutantInformation,
        [In] int MutantInformationLength,
        [Out] [Optional] out int ReturnLength
        );

    public enum MutantInformationClass : int
    {
        MutantBasicInformation
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MutantBasicInformation
    {
        public int CurrentCount;
        [MarshalAs(UnmanagedType.U1)]
        public bool OwnedByCaller;
        [MarshalAs(UnmanagedType.U1)]
        public bool AbandonedState;
    }

    #endregion

}

This won’t benefit the original poster of the question but here it goes.

While I do not disagree with other posters on proper use of Mutexes, I had an application where I needed to test whether someone owns a mutex without taking ownership myself. As mentioned by others the only way is to use an undocumented NtQueryMutant system call from ntdll.dll. I created an extension method for the Mutex class that can be used like this:

        bool createdNew = true;
        var m = new Mutex(false, MutexName, out createdNew);
        if ( m != null)
        {
            int currentCount;
            bool ownedByCaller, abandonedState;
            if (m.TryQuery(out currentCount, out ownedByCaller, out abandonedState))
            {
                Console.WriteLine(string.Format("Created New: {3}, Count: {0}, OwvedByMe: {1}, Abandoned: {2}",
                    currentCount, ownedByCaller, abandonedState, createdNew));
            }
            m.Close();
        }

And here is the implementation

public static class MutexExtensionMethods
{
    public static bool TryQuery(this Mutex m, out int currentCount, out bool ownedByCaller, out bool abandonedState)
    {
        currentCount = -1;
        ownedByCaller = abandonedState = false;
        try
        {
            var handle = m.SafeWaitHandle;
            if (handle != null)
            {
                var h = handle.DangerousGetHandle();
                MutantBasicInformation mbi;
                int retLength;
                var ntStatus = NtQueryMutant(
                    h,
                    MutantInformationClass.MutantBasicInformation,
                    out mbi, 
                    Marshal.SizeOf(typeof(MutantBasicInformation)),
                    out retLength);
                GC.KeepAlive(handle); // Prevent "handle" from being collected before NtQueryMutant returns
                if (ntStatus == 0)
                {
                    currentCount   = mbi.CurrentCount;
                    ownedByCaller  = mbi.OwnedByCaller;
                    abandonedState = mbi.AbandonedState;
                    return true;
                }
            }
        }
        catch
        {
        }
        return false;
    }

    #region NTDLL.DLL

    [DllImport("ntdll.dll")]
    public static extern uint NtQueryMutant(
        [In] IntPtr MutantHandle,
        [In] MutantInformationClass MutantInformationClass,
        [Out] out MutantBasicInformation MutantInformation,
        [In] int MutantInformationLength,
        [Out] [Optional] out int ReturnLength
        );

    public enum MutantInformationClass : int
    {
        MutantBasicInformation
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct MutantBasicInformation
    {
        public int CurrentCount;
        [MarshalAs(UnmanagedType.U1)]
        public bool OwnedByCaller;
        [MarshalAs(UnmanagedType.U1)]
        public bool AbandonedState;
    }

    #endregion

}
朮生 2024-09-12 06:12:05

.NET Mutex 类是本机互斥体包装器,它提供了与本机互斥体 API 相同的可能性(除了等待不同类型的可等待对象的数量)。如果您想在不阻塞的情况下获取互斥锁,请调用mutex.WaitOne(0)。使用 PInvoke,您可以调用 WaitForSingleObject,得到相同的结果。

.NET Mutex class is native mutex wrapper, which gives the same possibilities, as native mutex API (except waiting for number of waitable objects of different type). If you want to acquire mutex without blocking, call mutex.WaitOne(0). Using PInvoke, you can call WaitForSingleObject, with the same result.

青萝楚歌 2024-09-12 06:12:05

如果您确实尝试进行进程间锁定,顾名思义,您将需要一种方法来检测互斥锁是否实际上已被获取,对吗?我不确定如果没有 IsAcquired 属性,如何确保使用 InterProcessLock 的代码被锁定。 (此外,为了防止程序员意外调用 Dispose 两次,我在 Dispose 方法中将 IsAcquired 设置为 false

)我自己实现了同样的事情(因为我更喜欢使用 using 块而不是 try-finally 只是为了释放互斥体),而是在超过超时时抛出异常,如果我正确地记得该项目,则不会调用处置方法。

编辑:
在构造函数中抛出异常的额外好处:您的临界区也完全避免了,并且您可以在 catch 块中执行错误处理,无论如何,这可能包括与您的临界区相同的方法调用,尽管我个人认为这是一种不好的做法。

经过进一步思考,您可以在处置中使用以下内容,而不是使用另一个答案中指定的 try ... catch :

public void Dispose()
{
    if (IsAcquired)
    {
        lock (mutex) 
        {
            mutex.ReleaseMutex();
            IsAcquired = false;
        }
    }
}

锁定互斥体感觉有点讽刺,但是你已经有了。虽然我完全同意您不应该依赖于 IDisposable 接口的文档而调用 Dispose,但我认为拥有一个由 using() { }块。

If you're really trying to do an inter-process lock, as the name implies, you will want a way to detect if the Mutex has actually been acquired anyway, correct? I'm not sure how your code that uses your InterProcessLock would be ensured to be locked if there was no IsAcquired property. (Also, to protect against programmers who accidentally call Dispose twice, I'd set the IsAcquired to false in your Dispose method.)

I've implemented the same thing myself (because I much prefer the using block to a try-finally just to release the mutex) and instead threw an exception when the timeout was exceeded, which, if I'm remembering the project correctly, did not call the Dispose method.

Edit:
Added benefit of throwing the exception in the constructor: your critical section is also completely avoided, and you can perform error handling in the catch block, which could include the same method call your critical section had, anyway, though I personally would consider that a bad practice.

Upon further reflection, rather than using try ... catch as specified in another answer, you could use the following on your dispose:

public void Dispose()
{
    if (IsAcquired)
    {
        lock (mutex) 
        {
            mutex.ReleaseMutex();
            IsAcquired = false;
        }
    }
}

It feels a little ironic to lock a mutex, but there you have it. While I totally agree that you shouldn't rely on Dispose being called because of the documentation with the IDisposable interface, I think it is incredibly convenient to have an inter-process critical section indicated by a using() { } block.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文