程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> .Net下跟蹤線程掛起和程序死循環

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

編輯:關於.NET

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

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

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

3. 線程因未知原因掛起,比如死鎖。

4. 程序死循環。

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

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

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

        public ThreadMonitor()
        {
            _MonitorThread = new Thread(new ThreadStart(MonitorTask));
            _MonitorThread.Priority = ThreadPriority.Highest;
            _MonitorThread.IsBackground = true;

        }

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

Start  方法讓調用者啟動監控

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

Heartbeat 方法後面說明

        /**//// <summary>
        /// Start monitor
        /// </summary>
        public void Start()
        {
            _MonitorThread.Start();
        }


        /**//// <summary>
        /// Monitor register
        /// </summary>
        /// <param name="monitorPara">Monitor parameter</param>
        public void Register(MonitorParameter monitorPara)
        {
            Debug.Assert(monitorPara != null);
            Debug.Assert(monitorPara.Thread != null);

             if (GetTCB(monitorPara.Thread) != null)
            {
                throw new System.ArgumentException("Register repeatedly!");
            }

            lock (_RegisterLock)
            {
                _TCBTable.Add(monitorPara.Thread.ManagedThreadId, new TCB (monitorPara));
            }
        }

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

            tcb.LastHeartbeat = DateTime.Now;
            tcb.HitTimes = 0;
            tcb.Status &= ~ThreadStatus.Hang;
        }

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

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

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

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

            t.Suspend();
            StackTrace stack = new StackTrace(t, needFileInfo);
            t.Resume();

            return stack.ToString ();
        }

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

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

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

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

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

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

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

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

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

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

2:38:40 PM
ThreadMonitorEvent
Thread Name:Main thread
Thread Status:Hang
Thread Stack:   at System.Threading.Thread.SleepInternal(Int32 millisecondsTimeout)
   at System.Threading.Thread.Sleep(Int32 millisecondsTimeout)
   at DotNetDebug.Form1.buttonHang_Click(Object sender, EventArgs e)
   at System.Windows.Forms.Control.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ButtonBase.WndProc(Message& m)
   at System.Windows.Forms.Button.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
   at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.I MsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
   at System.Windows.Forms.Application.Run(Form mainForm)
   at DotNetDebug.Program.Main()
   at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()


2:38:40 PM
ThreadMonitorEvent
Thread Name:Counter thread
Thread Status:Hang
Thread Stack:   at System.Threading.WaitHandle.WaitOneNative(SafeWaitHandle waitHandle, UInt32 millisecondsTimeout, Boolean hasThreadAffinity, Boolean exitContext)
   at System.Threading.WaitHandle.WaitOne(Int64 timeout, Boolean exitContext)
   at System.Threading.WaitHandle.WaitOne(Int32 millisecondsTimeout, Boolean exitContext)
   at System.Windows.Forms.Control.WaitForWaitHandle(WaitHandle waitHandle)
   at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
   at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
   at System.Windows.Forms.Control.Invoke(Delegate method)
   at DotNetDebug.Form1.Counter()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

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

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

2:37:51 PM
ThreadMonitorEvent
Thread Name:Counter thread
Thread Status:Hang
Thread Stack:   at DotNetDebug.Form1.DoDeadCycle()
   at DotNetDebug.Form1.Counter()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()


2:37:52 PM
ThreadMonitorEvent
Thread Name:Counter thread
Thread Status:Hang, DeadCycle
Thread Stack:   at DotNetDebug.Form1.DoDeadCycle()
   at DotNetDebug.Form1.Counter()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

下面是示例代碼在

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

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using Sys.Diagnostics;

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

        delegate void CounterDelegate ();

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

        private void Counter()
        {
            int count = 0;
            while (true)
            {
                DoDeadCycle();
                labelCounter.Invoke(new CounterDelegate(delegate() { labelCounter.Text = (count++).ToString(); }));
                _ThreadMonitor.Heartbeat(Thread.CurrentThread);

                 Thread.Sleep(1000);
            }
        }

        public Form1()
        {
            InitializeComponent();
        }

        void OnThreadMonitorEvent(object sender, ThreadMonitor.ThreadMonitorEvent args)
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine(DateTime.Now.ToLongTimeString());
            sb.AppendLine("ThreadMonitorEvent");
            sb.AppendLine("Thread Name:" + args.Name);
            sb.AppendLine("Thread Status:" + args.Status.ToString());
            sb.AppendLine("Thread Stack:" + args.StackTrace);

             using (System.IO.FileStream fs =
                new System.IO.FileStream("report.log", System.IO.FileMode.Append,
                System.IO.FileAccess.Write))
            {
                using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fs))
                {
                    sw.WriteLine(sb.ToString());
                }
            }
        }


        private void Form1_Load(object sender, EventArgs e)
        {
            _ThreadMonitor.ThradMonitorEventHandler +=
                new EventHandler<ThreadMonitor.ThreadMonitorEvent> (OnThreadMonitorEvent);

            _CounterThread = new Thread(new ThreadStart(Counter));
            _CounterThread.IsBackground = true;


            _ThreadMonitor.Register(new ThreadMonitor.MonitorParameter(
                Thread.CurrentThread, "Main thread", 10000, 5000,
                ThreadMonitor.MonitorFlag.MonitorHang |
                ThreadMonitor.MonitorFlag.MonitorDeadCycle));

             _ThreadMonitor.Register(new ThreadMonitor.MonitorParameter(
                _CounterThread, "Counter thread",
                ThreadMonitor.MonitorFlag.MonitorHang |
                ThreadMonitor.MonitorFlag.MonitorDeadCycle));

             _CounterThread.Start();

            timerHeartbeat.Interval = 1000;
            timerHeartbeat.Enabled = true;

            _ThreadMonitor.Start();
        }

        private void timerHeartBeat_Tick(object sender, EventArgs e)
        {
            _ThreadMonitor.Heartbeat(Thread.CurrentThread);
        }

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

        private void buttonHang_Click(object sender, EventArgs e)
        {
            Thread.Sleep(20000);
        }
    }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved