【问题标题】:How to find that Mutex in C# is acquired?如何找到C#中的Mutex被获取?
【发布时间】:2011-03-02 02:09:49
【问题描述】:

如何从 C# 中的 mutex 句柄中找到已获取的 mutex?

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()如果不是我获取的就会抛出异常。

(通过 acquired 状态是指当我拥有互斥体时,互斥体处于未发出信号状态。)

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

【问题讨论】:

    标签: c# synchronization pinvoke mutex handle


    【解决方案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
    
    }
    

    【讨论】:

    • 我添加了一个缺失的(必需的)GC.KeepAlive 呼叫。更好的是只声明 NtQueryMutant 以使用 SafeWaitHandle 参数。
    【解决方案2】:

    您可能会发现,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 中。这些允许访问系统对象。但是,这些功能可能会在未来的操作系统版本中发生变化或不可用。

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

    【讨论】:

      【解决方案3】:

      为什么不能使用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)) 怎么办?不会调用 Dispose。如果用户根本不调用 Dispose 怎么办?

      会有一个废弃的互斥锁

      来自MSDN

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

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

      一个可能的例子是

      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,因为 Mutex 是一种操作系统构造。如果名称不是 unquie,它可能会影响使用相同互斥体名称的其他进程。

      【讨论】:

      • 有什么用?这只是检查互斥锁是否存在。不是被收购了。
      • 感谢您提示传递闭包比IDisposable.Dispose 或自定义Release method 更安全地防止用户错误。
      【解决方案4】:

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

      我自己也实现了同样的事情(因为我更喜欢 using 块而不是 try-finally 只是为了释放互斥锁)而是在超过超时时抛出异常,如果我记得项目的话正确,没有调用 Dispose 方法。

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

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

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

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

      【讨论】:

      • 如果程序员调用Dispose()两次,会抛出异常。所以他们可以发现他们的代码中有一个错误。早些时候,我也在构造函数中抛出了一个异常。但后来我改变了这一点,因为即使没有获得锁,程序员也可能在某些情况下决定运行代码。 (例如,有独立的进程,可能一个被卡住了,所以第二个等待一段时间,然后它会运行。)
      • IDisposable 设计文档规定“如果多次调用对象的 Dispose 方法,则该对象必须忽略第一次调用之后的所有调用。如果调用其 Dispose 方法,则该对象不得抛出异常多次。” msdn.microsoft.com/en-us/library/…
      • +1 啊,好的。感谢您的信息。我还没有读过:(
      【解决方案5】:

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

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

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

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

      【讨论】:

      • Karl - 我同意你的所有陈述,除了构造函数/处置部分; Dispose 与析构函数不同 - 当涉及到 IL 时,using 语句由 try/finally 块实现。 (如下所述,我认为构造函数在获取锁失败时总是抛出异常以保证临界区没有运行。)
      • Matt - msdn.microsoft.com/en-us/library/b1yfkh5e.aspx: “不要假设 Dispose 会被调用。”你是对的,只要开发人员在 using 块中限定它或手动调用 dispose 就可以了。但这很尴尬。许多人会假设一个终结器(或者更可能只是不知道......)并让它脱离范围。这不适用于锁,因为 GC 可能会也可能不会立即收集对象,从而导致锁定异常。它创建了很难找到的错误。它会起作用,但它会为简单的锁增加“这样做”的过程开销。最好让锁像锁一样。
      • 嗯,我还没读过。我想知道为什么 IDisposable.Dispose 文档页面上没有提到这一点。 +1 - 我认为你的答案应该被接受。
      • Karl,我可能无法正确理解 - 如果我添加一个将调用 Dispose()Release() 方法,可以吗?接下来如何使用AutoResetEvent来同步进程?
      • 我是说根本不要使用 dispose 接口。有一个静态方法来创建您需要的互斥对象(工厂模式)。然后直接使用这些互斥锁而不是包装它。我错过了引用,它不是 AutoResetEvent,而是 EventWaitHandle。您可以创建或等待系统事件,直到另一个进程发出信号。您可能想澄清您要解决的问题,我认为您可能会得到更好的答案。
      【解决方案6】:

      嗯,这不是您所要求的,但我认为它会解决您的问题:为什么不专门针对其他人获取 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.
              }
      }
      

      【讨论】:

      • 是的,谢谢,也是一个解决方案,但如果可能的话,我只是想以“更好”的方式解决问题:)
      • 从安全的角度来看,这是防止竞争条件的唯一方法。您可以在多线程环境中进行的任何检查都将失败,因为您可以在世界上进行所有检查,然后线程会在您释放导致异常时获取。这可能是最安全的模式。
      • 不确定。因为,我认为在同一进程中的线程之间使用进程间锁不是一个好主意。所以,如果一个线程在另一个线程刚刚释放它时获得了全局进程锁,我想抛出一个异常。
      【解决方案7】:

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

      【讨论】:

      • 谢谢,但如果尚未获得互斥体,我不想获得它。有什么功能吗?
      • 不,在 .NET 互斥锁和原生 API 中都没有这种方法。只有在没有被任何其他线程获取的情况下,才能获取互斥锁。可能您需要其他一些同步类型。定义您的要求,也许其他一些同步类型(如事件或信号量)可以满足它们。
      • 我把答案放到帖子里(因为我的答案更长)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-05
      • 2011-07-20
      • 1970-01-01
      • 2018-02-09
      • 2014-07-16
      • 1970-01-01
      相关资源
      最近更新 更多