【问题标题】:How to know which thread has lock preferably using Visual Studio debugger (.net programming)?如何最好使用 Visual Studio 调试器(.net 编程)知道哪个线程有锁?
【发布时间】:2022-02-18 16:09:38
【问题描述】:

我只是使用简单的锁来管理一些共享数据。

有没有办法知道哪个线程获得了锁?

基本上有人已经获得了锁并且没有释放它。因此,所有后续操作都只是在锁定和超时时触发。

我有点卡住了,因为从调试器开始它只是卡在锁定状态,因为有人已经获取了它,我在“Break All”之后查看了“Debugger + Windows+ Threads” - 没有线程进入锁定。

它没有显示任何进入临界区的线程。

有可能有人获得了锁并且该线程已被中止。但我希望即使线程已中止,锁也会被释放。我的预期错了吗?


这是我的课程 - 基本上它是一个 powershell cmd 运行程序,并且可以从多个线程执行 cmd:

internal abstract class PowerShellCommandRunner : IDisposable
{
    #region Fields
    protected object m_syncObject = new object();
    private PSSession m_psSession = null;
    private Runspace m_runspace = null;
    #endregion

    #region Constructor
    public PowerShellCommandRunner(ExchangeApplicationSystem system)
    {
        if (null == system)
        {
            throw new ArgumentNullException("ExchangeApplicationSystem");
        }
        this.ExchangeApplicationSystem = system;
        this.PSCredential = this.ExchangeApplicationSystem.Credential.GetPSCredential();
    }
    #endregion

    #region Properties


    internal ExchangeApplicationSystem ExchangeApplicationSystem
    {
        get;
        private set;
    }

    public PSCredential PSCredential
    {
        get;
        protected set;
    }

    private bool IsNotInitializedOrInvalidRunspace(Runspace runspace)
    {
        bool flag = (null == runspace) //not initialized
            || (null == runspace.RunspaceStateInfo) //not state info (defensive)
            || (runspace.RunspaceStateInfo.State == RunspaceState.Broken) //runspace state is broken
            || (null != runspace.RunspaceStateInfo.Reason); //there is an exception
        return flag;
    }

    private bool NeedToCreatePsSession
    {
        get
        {
            bool flag = (null == this.m_psSession)
                || this.IsNotInitializedOrInvalidRunspace(this.m_psSession.Runspace);
            return flag;
        }
    }

    internal Runspace Runspace
    {
        get
        {
            lock (this.m_syncObject)
            {
                if (this.IsNotInitializedOrInvalidRunspace(this.m_runspace))
                {
                    if (null != this.m_runspace)
                    {
                        //already have one runspace - close it, before we create another one
                        this.CloseRunspace();
                    }
                    this.m_runspace = RunspaceFactory.CreateRunspace();
                    this.m_runspace.Open();
                }
                return this.m_runspace;
            }
        }
    }
    #endregion

    #region Methods

    internal IEnumerable<PSObject> Execute(string cmd, params object[] argumentList)
    {
        if (string.IsNullOrEmpty(cmd))
        {
            throw new ArgumentNullException("cmd");
        }
        return this.Execute(new Command(cmd), argumentList);
    }

    /// <summary>
    /// Sub-classes can do their own specific implementation to create ps-sessions
    /// The base class simply performs primitive oepratiosn like managing them.
    /// </summary>
    /// <returns></returns>
    internal abstract PSSession GetPSSession();

    /// <summary>
    /// Gets the pssession and if reuired updates it as well
    /// </summary>
    /// <returns></returns>        
    private PSSession GetAndUpdateManagePSSessionInfRequired()
    {
        //Note: we dont need to lock as the callers (Exceute methods) will be acquiring the lock before executing the code
        //but, just locking it again as the locks are re-entrant
        lock (this.m_syncObject)
        {
            if (this.NeedToCreatePsSession)
            {
                if (null != this.m_psSession
                    && (null != this.m_runspace))
                {
                    //if ps-session exists, remove it from runspace
                    this.RemovePsSessionFromRunspace();
                    //Yes, there can be a case where some one already have a reference to the remove session
                    //that's ok, as the operation simply throws
                    //And subsequently they will be releasing and re-using the new one.
                    this.m_psSession = null;
                }
                //now, open a new session (requesting for a new session from subclasses)
                this.m_psSession = this.GetPSSession();
                Debug.Assert(null != this.m_psSession);
            }
            return this.m_psSession;
        }
    }

    internal IEnumerable<PSObject> Execute(Command cmd, params object[] argumentList)
    {
        if (null == cmd)
        {
            throw new ArgumentNullException("cmd");
        }
        lock (this.m_syncObject)
        {
            //Pipelines cannot be executed concurrently, so serialize it                
            OperationProgressReporter.Report(string.Format("Executing the following PowerShell Command: {0}", cmd.ToString()));
            return this.Runspace.ExecuteCommand(cmd, this.GetAndUpdateManagePSSessionInfRequired(), argumentList);
        }
    }

    internal IEnumerable<PSObject> Execute(ScriptBlock sb, params object[] argumentList)
    {
        if (null == sb)
        {
            throw new ArgumentNullException("scriptblock");
        }
        lock (this.m_syncObject)
        {
            //Pipelines cannot be executed concurrently, so serialize it                
            OperationProgressReporter.Report(string.Format("Executing the following PowerShell Command: {0}", sb.ToString()));
            return this.Runspace.ExecuteCommand(sb, this.GetAndUpdateManagePSSessionInfRequired(), argumentList);
        }
    }

    private void RemovePsSessionFromRunspace()
    {
        //not intended to call before acquiring a lock 
        //(For ex: either while closing runspace or while getting rid of old session and getting a new one (GetAndUpdateManagePSSessionInfRequired)
        //but locking it as the locks are re-entrant (defensive)
        lock (this.m_syncObject)
        {
            if ((null != this.m_psSession)
                && (null != this.m_runspace))
            {
                try
                {
                    string errorMsg = null;
                    this.m_runspace.RemovePsSessionFromRunspace(this.m_psSession, out errorMsg);
                    if (!string.IsNullOrEmpty(errorMsg))
                    {
                        FxTracing.TraceError(TraceEventId.GeneralError, errorMsg);
                    }
                }
                catch (Exception ex)
                {
                    ExceptionManager.GeneralExceptionFilter(ex);
                    OperationProgressReporter.Report(OperationProgressMessageLevel.Verbose,
                        string.Format("<DEBUG> Unable to remove PSsession from runspace in '{0}'", this.ExchangeApplicationSystem));
                }
            }
        }
    }

    private void CloseRunspace()
    {
        //lcok it
        lock (this.m_syncObject)
        {
            //check again to make sure only one thread enters
            if (null != this.m_runspace)
            {
                try
                {
                    //if a ps-session is created, remove it
                    if (null != this.m_psSession)
                    {
                        //remove the pssession from runspace
                        this.RemovePsSessionFromRunspace();
                    }
                    //then close the runspace
                    this.m_runspace.Close();
                }
                catch (Exception ex)
                {
                    //swallow
                    ExceptionManager.GeneralExceptionFilter(ex);
                    Debug.Fail(ex.Message);
                }
                finally
                {
                    //finally, set the runspace to null
                    //Yes, the runspace can be set to null while another thread can have a reference to old runspace
                    //its ok as the operation simply fail with invalid runspace state exception (most likely)
                    //And when they retry they get the updated runspace or get a new one.
                    //same appraoch as managing ps-session
                    this.m_runspace = null;
                }
            }
        }
    }
    #endregion

    #region IDisposable
    public void Dispose()
    {
        this.CloseRunspace();
    }
    #endregion
}

【问题讨论】:

  • 可以分享代码吗?
  • 请提供有关您的线程模型和锁的更多信息。首选代码。
  • 当然,添加代码。
  • 您期望即使持有它的线程被中止也会释放锁是合理的。任何未执行的finally 块都会在线程中止之前执行(参见msdn.microsoft.com/en-us/library/ty8d3wta.aspx)。请注意,如果调用 .Abort() 的线程持有其他线程的 finally 区域所需的任何资源的锁,这可能会导致死锁。
  • 您显然已经对这段代码进行了一些思考。不过有一件事引起了我的注意(这纯粹是猜测):您正在从许多受锁保护的代码区域调用OperationProgressReporter.Report。这可能会执行一些外部代码。如果该代码碰巧将同步消息分派到另一个同步上下文,而后者又调用同一个 PowerShellCommandRunner 实例上的方法或属性访问器之一(命中同一个锁),那么您就陷入了死锁。

标签: c# .net visual-studio


【解决方案1】:

也许这会有所帮助:

  1. 启用非托管调试(项目属性 > 调试 > 选中“启用非托管代码调试”
  2. 在某处设置断点
  3. 当执行中断时,在立即窗口中输入:.load sos,然后输入!SyncBlk -a

【讨论】:

    【解决方案2】:

    只需使用Debug.WriteLine 添加一些诊断打印消息即可查看哪个线程已进入临界区。

    http://msdn.microsoft.com/en-us/library/system.diagnostics.debug.writeline.aspx

    【讨论】:

      【解决方案3】:

      这是一个很老的问题......但我发现在 VS 2022 中,Parallel Stacks 窗口会显示:

      非常方便。感谢 VS 团队!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-15
        • 1970-01-01
        • 2017-05-23
        • 2023-03-14
        • 2017-08-28
        相关资源
        最近更新 更多