熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

.Net 下跟蹤線程掛起和程序死循環

2013-11-13 10:01:07  來源: .NET編程 

  下的程序調試相對C/C++要簡單很多少了那些令人頭疼的指針越界的問題不過當你的程序遇到如下問題時依然非常棘手

  進程異常終止解決方案見 Net 下未捕獲異常的處理

  內存洩漏或者內存申請後程序始終沒有釋放解決方案見 用 NET Memory Profiler 跟 應用內存使用情況基本應用篇 如果通過自己編寫的程序監控我將在以後的文章中闡述

  線程因未知原因掛起比如死鎖

  程序死循環

  本文將闡述如果編寫程序對後兩者故障實時跟蹤並報告

  首先我們需要一個單獨的監控線程來監控需要監控的線程

  我做了一個監控類 ThreadMonitor在開始監控之前我們將監控線程的優先級設置為最高

          public ThreadMonitor()
        {
            _MonitorThread = new Thread(new ThreadStart(MonitorTask));
            _MonitorThreadPriority = ThreadPriorityHighest;
            _MonitorThreadIsBackground = true;

        }

  接下來我們為這個線程提供幾個公共方法

  方法讓調用者啟動監控

  方法用於將需要監控的線程注冊到監控列表中

  方法後面說明

  /**//// 
        /// Start monitor
        /// 
        public void Start()
        {
            _MonitorThreadStart();
        }


        /**//// 
        /// Monitor register
        /// 
        /// Monitor parameter
        public void Register(MonitorParameter monitorPara)
        {
            DebugAssert(monitorPara != null);
            DebugAssert(monitorParaThread != null);

            if (GetTCB(monitorParaThread) != null)
            {
                throw new SystemArgumentException(Register repeatedly!);
            }

            lock (_RegisterLock)
            {
                _TCBTableAdd(monitorParaThreadManagedThreadId new TCB(monitorPara));
            }
        }

        public void Heartbeat(Thread t)
        {
            TCB tcb = GetTCB(t);
            if (tcb == null)
            {
                throw new SystemArgumentException(This thread was not registered!);
            }

            tcbLastHeartbeat = DateTimeNow;
            tcbHitTimes = ;
            tcbStatus &= ~ThreadStatusHang;
        }

  下面讓我來說說如何監控某個線程掛起

  監控線程提供了一個心跳調用 Heartbeat 被監控的線程必須設置一個定時器定時向監控線程發送心跳如果監控線程在一定時間內無法收到這個心跳消息則認為被監控線程非正常掛起了這個時間又MonitorParameter參數的HangTimeout指定

  光監控到線程掛起還不夠我們必須要報告線程當前掛起的位置才有實際意義那麼如何獲得線程當前的調用位置呢?Net framework 為我們提供了獲取線程當前堆棧調用回溯的方法見下面代碼

  private string GetThreadStackTrace(Thread t)
        {
            bool needFileInfo = NeedFileInfo;

            tSuspend();
            StackTrace stack = new StackTrace(t needFileInfo);
            tResume();

            return stackToString();
        }

  這裡需要說明的是StackTrace(t needFileInfo) 必須在線程t Suspend後 才能調用否則會發生異常但ThreadSuspend 調用是比較危險的因為調用者無法知道線程t掛起前的運行狀況可能線程t目前正在等待某個資源這時強制掛起非常容易造成程序死鎖不過值得慶幸的是StackTrace(t needFileInfo)的調用不會和其他線程尤其是調用線程產生資源沖突但我們必須在這一句執行結束後迅速調用 tResume 結束線程t的掛起狀態

  談完了對線程非正常掛起的監控再談談對程序死循環的監控

  在決定采用我現在的這個方案之前我曾經想通過 GetThreadTimes 這個API 函數得到被監控線程的實際CPU運行時間通過這個時間來計算其CPU占有率但很遺憾我的嘗試失敗了通過非當前線程下調用 GetThreadTimes 無法得到對應線程的CPU時間(好像非托管線程可以Net的托管線程我試了確實不行但原因我還沒弄明白)另外GetThreadTimes 統計不夠准確 見 對老趙寫的簡單性能計數器的修改續 關於

  所以沒有辦法我采用了一個不是很理想的方案

  定時統計當前進程的TotalProcessorTime 來計算當前線程的CPU占有率如果這個CPU占有率在一段時間內大於 / (CPU 數)* % 則認為當前進程出現了死循環這個測試時間由  MonitorParameter參數的DeadCycleTimeout 屬性指定

  這就出現了一個問題我們只知道程序死循環了但不知道具體是那個線程死循環那麼如何找到真正死循環的線程呢?

  我采用的方法是每秒鐘檢測一次線程當前狀態如果當前狀態為運行狀態則表示命中一次在確認出現死循環後我們在來檢查在一個檢查周期內的命中次數如果這個命中次數足夠高則認為是該線程死循環了不過這樣還是有問題主線程在等待windows 消息時 或者控制台程序線程在等待控制台輸入時該線程的狀態居然始終是 Runing 其實是阻塞了但我沒有找到一個很好的方法來得到線程當前處於阻塞狀態怎麼辦?我想了個笨辦法就是在上面兩個條件都符合的情況下再看看在此期間有沒有心跳如果沒有心跳說明死循環了但如果有心跳也不一定就沒有死循環遇到這種情況就將可疑的都全部報告了靠人來判斷吧

  我寫了一個示例代碼代碼中有一個Winform 主線程 和 一個計數器線程計數器線程每秒記一次數並更新界面監控線程檢查到非正常掛起或者死循環將在當前目錄下寫一個Reportlog 輸出監控報告

  點擊Hang後主線程休眠計數器線程由於要更新界面也同樣會被掛起

  監控線程檢查到兩個線程掛起後報告如下

  ThreadMonitorEvent
Thread Name:Main thread
Thread Status:Hang
Thread Stack:   at SystemThreadingThreadSleepInternal(Int millisecondsTimeout)
   at SystemThreadingThreadSleep(Int millisecondsTimeout)
   at DotNetDebugFormbuttonHang_Click(Object sender EventArgs e)
   at SystemWindowsFormsControlOnClick(EventArgs e)
   at SystemWindowsFormsButtonOnClick(EventArgs e)
   at SystemWindowsFormsButtonOnMouseUp(MouseEventArgs mevent)
   at SystemWindowsFormsControlWmMouseUp(Message& m MouseButtons button Int clicks)
   at SystemWindowsFormsControlWndProc(Message& m)
   at SystemWindowsFormsButtonBaseWndProc(Message& m)
   at SystemWindowsFormsButtonWndProc(Message& m)
   at SystemWindowsFormsControlControlNativeWindowOnMessage(Message& m)
   at SystemWindowsFormsControlControlNativeWindowWndProc(Message& m)
   at SystemWindowsFormsNativeWindowDebuggableCallback(IntPtr hWnd Int msg IntPtr wparam IntPtr lparam)
   at SystemWindowsFormsUnsafeNativeMethodsDispatchMessageW(MSG& msg)
   at SystemWindowsFormsApplicationComponentManagerSystemWindowsFormsUnsafeNativeMethodsIMsoComponentManagerFPushMessageLoop(Int dwComponentID Int reason Int pvLoopData)
   at SystemWindowsFormsApplicationThreadContextRunMessageLoopInner(Int reason ApplicationContext context)
   at SystemWindowsFormsApplicationThreadContextRunMessageLoop(Int reason ApplicationContext context)
   at SystemWindowsFormsApplicationRun(Form mainForm)
   at DotNetDebugProgramMain()
   at SystemAppDomain_nExecuteAssembly(Assembly assembly String[] args)
   at SystemAppDomainExecuteAssembly(String assemblyFile Evidence assemblySecurity String[] args)
   at MicrosoftVisualStudioHostingProcessHostProcRunUsersAssembly()
   at SystemThreadingThreadHelperThreadStart_Context(Object state)
   at SystemThreadingExecutionContextRun(ExecutionContext executionContext ContextCallback callback Object state)
   at SystemThreadingThreadHelperThreadStart()

  :: PM
ThreadMonitorEvent
Thread Name:Counter thread
Thread Status:Hang
Thread Stack:   at SystemThreadingWaitHandleWaitOneNative(SafeWaitHandle waitHandle UInt millisecondsTimeout Boolean hasThreadAffinity Boolean exitContext)
   at SystemThreadingWaitHandleWaitOne(Int timeout Boolean exitContext)
   at SystemThreadingWaitHandleWaitOne(Int millisecondsTimeout Boolean exitContext)
   at SystemWindowsFormsControlWaitForWaitHandle(WaitHandle waitHandle)
   at SystemWindowsFormsControlMarshaledInvoke(Control caller Delegate method Object[] args Boolean synchronous)
   at SystemWindowsFormsControlInvoke(Delegate method Object[] args)
   at SystemWindowsFormsControlInvoke(Delegate method)
   at DotNetDebugFormCounter()
   at SystemThreadingThreadHelperThreadStart_Context(Object state)
   at SystemThreadingExecutionContextRun(ExecutionContext executionContext ContextCallback callback Object state)
   at SystemThreadingThreadHelperThreadStart()

  點擊DeadCycle 按鈕後讓計數器線程死循環但主線程不死循環

  監控線程檢查到計數器線程死循環後報告如下

  :: PM
ThreadMonitorEvent
Thread Name:Counter thread
Thread Status:Hang
Thread Stack:   at DotNetDebugFormDoDeadCycle()
   at DotNetDebugFormCounter()
   at SystemThreadingThreadHelperThreadStart_Context(Object state)
   at SystemThreadingExecutionContextRun(ExecutionContext executionContext ContextCallback callback Object state)
   at SystemThreadingThreadHelperThreadStart()

  :: PM
ThreadMonitorEvent
Thread Name:Counter thread
Thread Status:Hang DeadCycle
Thread Stack:   at DotNetDebugFormDoDeadCycle()
   at DotNetDebugFormCounter()
   at SystemThreadingThreadHelperThreadStart_Context(Object state)
   at SystemThreadingExecutionContextRun(ExecutionContext executionContext ContextCallback callback Object state)
   at SystemThreadingThreadHelperThreadStart()

  下面是示例代碼在

  以下是測試代碼完整源碼的下載位置:  完整源碼  


using System;
using SystemCollectionsGeneric;
using SystemComponentModel;
using SystemData;
using SystemDrawing;
using SystemText;
using SystemWindowsForms;
using SystemThreading;
using SysDiagnostics;

namespace DotNetDebug
{
    public partial class Form : Form
    {
        Thread _CounterThread;
        ThreadMonitor _ThreadMonitor = new ThreadMonitor();
        bool _DeadCycle = false;

        delegate void CounterDelegate();

        private void DoDeadCycle()
        {
            while (_DeadCycle)
            {
            }
        }

        private void Counter()
        {
            int count = ;
            while (true)
            {
                DoDeadCycle();
                labelCounterInvoke(new CounterDelegate(delegate() { labelCounterText = (count++)ToString(); }));
                _ThreadMonitorHeartbeat(ThreadCurrentThread);

                ThreadSleep();
            }
        }

        public Form()
        {
            InitializeComponent();
        }

        void OnThreadMonitorEvent(object sender ThreadMonitorThreadMonitorEvent args)
        {
            StringBuilder sb = new StringBuilder();

            sbAppendLine(DateTimeNowToLongTimeString());
            sbAppendLine(ThreadMonitorEvent);
            sbAppendLine(Thread Name: + argsName);
            sbAppendLine(Thread Status: + argsStatusToString());
            sbAppendLine(Thread Stack: + argsStackTrace);

            using (SystemIOFileStream fs =
                new SystemIOFileStream(reportlog SystemIOFileModeAppend 
                SystemIOFileAccessWrite))
            {
                using (SystemIOStreamWriter sw = new SystemIOStreamWriter(fs))
                {
                    swWriteLine(sbToString());
                }
            }
        }


        private void Form_Load(object sender EventArgs e)
        {
            _ThreadMonitorThradMonitorEventHandler +=
                new EventHandler<ThreadMonitorThreadMonitorEvent>(OnThreadMonitorEvent);

            _CounterThread = new Thread(new ThreadStart(Counter));
            _CounterThreadIsBackground = true;


            _ThreadMonitorRegister(new ThreadMonitorMonitorParameter(
                ThreadCurrentThread Main thread  
                ThreadMonitorMonitorFlagMonitorHang |
                ThreadMonitorMonitorFlagMonitorDeadCycle));

            _ThreadMonitorRegister(new ThreadMonitorMonitorParameter(
                _CounterThread Counter thread
                ThreadMonitorMonitorFlagMonitorHang |
                ThreadMonitorMonitorFlagMonitorDeadCycle));

            _CounterThreadStart();

            timerHeartbeatInterval = ;
            timerHeartbeatEnabled = true;

            _ThreadMonitorStart();
        }

        private void timerHeartBeat_Tick(object sender EventArgs e)
        {
            _ThreadMonitorHeartbeat(ThreadCurrentThread);
        }

        private void ButtonDeadCycle_Click(object sender EventArgs e)
        {
            _DeadCycle = true;
        }

        private void buttonHang_Click(object sender EventArgs e)
        {
            ThreadSleep();
        }
    }
}
From:http://tw.wingwit.com/Article/program/net/201311/12301.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.