如何在C#中找到Mutex?

时间:2010-06-10 09:35:00

标签: c# synchronization pinvoke mutex handle

如何从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()会引发异常,如果我没有获取它。

(通过获取状态我的意思是当我拥有互斥锁时,互斥锁处于 not-signaled 状态。)

(编辑:由于mattdekrey's post,我添加了IsAcquired = false;。)

7 个答案:

答案 0 :(得分:13)

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

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

不要依赖Dispose来释放锁,这就是要求难以维护的死锁代码。使用try / finally块确保它被释放。

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

答案 1 :(得分:4)

如您所见,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中。这些允许访问系统对象。但是,这些功能可能会在未来版本的操作系统中更改或不可用。

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

答案 2 :(得分:2)

为什么不能使用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。

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

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))怎么办?不会调用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
            }
        }
    }
}

这是一种可能的方式。这一切都取决于目标是什么。因为Mutex是一个操作系统构造,所以我不会在最终用户上中继调用Dispose。如果名称不是unquie,则可能会影响使用相同互斥锁名称的其他进程。

答案 3 :(得分:1)

嗯,这不完全是你所要求的,但我认为它可以解决你的问题:为什么不只是添加一些错误处理专门针对在其他人获得Mutex时发生的异常?

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.
        }
}

答案 4 :(得分:1)

这不会使问题的原始海报受益,但在这里。

虽然我不同意其他正确使用互斥锁的海报,但我有一个应用程序,我需要测试某人是否拥有互斥锁而不自行取得所有权。正如其他人所提到的,唯一的方法是使用来自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

}

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

我自己实现了同样的事情(因为我更喜欢使用块来尝试 - 最后只是为了释放互斥锁)而是在超出超时时抛出异常,如果我记得这个项目没错,没有调用Dispose方法。

修改: 添加了在构造函数中抛出异常的好处:您的关键部分也完全被避免,并且您可以在catch块中执行错误处理,无论如何,这可能包括与您的关键部分相同的方法调用,尽管我个人会认为这是一种不好的做法。

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

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

lock互斥锁感觉有点讽刺,但是你有它。虽然我完全同意你不应该因为带有IDisposable接口的文档而依赖于调用Dispose,但我认为让using() { }块表示一个进程间关键部分非常方便。