轉(zhuǎn)帖|其它|編輯:郝浩|2010-06-18 11:10:48.000|閱讀 1355 次
概述:本文談一談.Net 下跟蹤線程掛起和程序死循環(huán)的解決方法。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
下的程序調(diào)試相對C/C++要簡單很多,少了那些令人頭疼的指針越界的問題。不過當(dāng)你的程序遇到如下問題時(shí),依然非常棘手:
進(jìn)程異常終止。解決方案見 .Net 下未捕獲異常的處理
內(nèi)存泄漏或者內(nèi)存申請后程序始終沒有釋放。解決方案見 用 .NET Memory Profiler 跟蹤.net 應(yīng)用內(nèi)存使用情況--基本應(yīng)用篇 。如果通過自己編寫的程序監(jiān)控,我將在以后的文章中闡述。
線程因未知原因掛起,比如死鎖。
程序死循環(huán)。
本文將闡述如果編寫程序?qū)髢烧吖收蠈?shí)時(shí)跟蹤并報(bào)告。
首先我們需要一個(gè)單獨(dú)的監(jiān)控線程來監(jiān)控需要監(jiān)控的線程
我做了一個(gè)監(jiān)控類 ThreadMonitor,在開始監(jiān)控之前,我們將監(jiān)控線程的優(yōu)先級設(shè)置為最高。
public ThreadMonitor() { _MonitorThread = new Thread(new ThreadStart(MonitorTask)); _MonitorThread.Priority = ThreadPriority.Highest; _MonitorThread.IsBackground = true; } |
接下來我們?yōu)檫@個(gè)線程提供幾個(gè)公共方法
方法讓調(diào)用者啟動監(jiān)控
方法用于將需要監(jiān)控的線程注冊到監(jiān)控列表中
方法后面說明
/**//// /// Start monitor /// public void Start() { _MonitorThread.Start(); } /**//// /// Monitor register /// /// Monitor parameter 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; } |
下面讓我來說說如何監(jiān)控某個(gè)線程掛起。
監(jiān)控線程提供了一個(gè)心跳調(diào)用 Heartbeat ,被監(jiān)控的線程必須設(shè)置一個(gè)定時(shí)器定時(shí)向監(jiān)控線程發(fā)送心跳,如果監(jiān)控線程在一定時(shí)間內(nèi)無法收到這個(gè)心跳消息,則認(rèn)為被監(jiān)控線程非正常掛起了。這個(gè)時(shí)間又MonitorParameter參數(shù)的HangTimeout指定。
光監(jiān)控到線程掛起還不夠,我們必須要報(bào)告線程當(dāng)前掛起的位置才有實(shí)際意義。那么如何獲得線程當(dāng)前的調(diào)用位置呢?.Net framework 為我們提供了獲取線程當(dāng)前堆棧調(diào)用回溯的方法。見下面代碼
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后 才能調(diào)用,否則會發(fā)生異常。但Thread.Suspend 調(diào)用是比較危險(xiǎn)的,因?yàn)檎{(diào)用者無法知道線程t掛起前的運(yùn)行狀況,可能線程t目前正在等待某個(gè)資源,這時(shí)強(qiáng)制掛起,非常容易造成程序死鎖。不過值得慶幸的是StackTrace(t, needFileInfo)的調(diào)用不會和其他線程尤其是調(diào)用線程產(chǎn)生資源沖突,但我們必須在這一句執(zhí)行結(jié)束后迅速調(diào)用 t.Resume 結(jié)束線程t的掛起狀態(tài)。
談完了對線程非正常掛起的監(jiān)控,再談?wù)剬Τ绦蛩姥h(huán)的監(jiān)控。
在決定采用我現(xiàn)在的這個(gè)方案之前,我曾經(jīng)想通過 GetThreadTimes 這個(gè)API 函數(shù)得到被監(jiān)控線程的實(shí)際CPU運(yùn)行時(shí)間,通過這個(gè)時(shí)間來計(jì)算其CPU占有率,但很遺憾,我的嘗試失敗了。通過非當(dāng)前線程下調(diào)用 GetThreadTimes 無法得到對應(yīng)線程的CPU時(shí)間。(好像非托管線程可以,但.Net的托管線程我試了,確實(shí)不行,但原因我還沒弄明白)另外GetThreadTimes 統(tǒng)計(jì)不夠準(zhǔn)確 見 對老趙寫的簡單性能計(jì)數(shù)器的修改續(xù)- 關(guān)于
所以沒有辦法,我采用了一個(gè)不是很理想的方案
定時(shí)統(tǒng)計(jì)當(dāng)前進(jìn)程的TotalProcessorTime 來計(jì)算當(dāng)前線程的CPU占有率,如果這個(gè)CPU占有率在一段時(shí)間內(nèi)大于 100 / (CPU 數(shù))* 90% ,則認(rèn)為當(dāng)前進(jìn)程出現(xiàn)了死循環(huán)。這個(gè)測試時(shí)間由 MonitorParameter參數(shù)的DeadCycleTimeout 屬性指定。
這就出現(xiàn)了一個(gè)問題,我們只知道程序死循環(huán)了,但不知道具體是那個(gè)線程死循環(huán),那么如何找到真正死循環(huán)的線程呢?
我采用的方法是每秒鐘檢測一次線程當(dāng)前狀態(tài),如果當(dāng)前狀態(tài)為運(yùn)行狀態(tài)則表示命中一次,在確認(rèn)出現(xiàn)死循環(huán)后我們在來檢查在一個(gè)檢查周期內(nèi)的命中次數(shù),如果這個(gè)命中次數(shù)足夠高,則認(rèn)為是該線程死循環(huán)了。不過這樣還是有問題,主線程在等待windows 消息時(shí) 或者控制臺程序線程在等待控制臺輸入時(shí),該線程的狀態(tài)居然始終是 Runing ,其實(shí)是阻塞了,但我沒有找到一個(gè)很好的方法來得到線程當(dāng)前處于阻塞狀態(tài)。怎么辦?我想了個(gè)笨辦法,就是在上面兩個(gè)條件都符合的情況下再看看在此期間有沒有心跳,如果沒有心跳,說明死循環(huán)了。但如果有心跳也不一定就沒有死循環(huán),遇到這種情況,就將可疑的都全部報(bào)告了,靠人來判斷吧。
我寫了一個(gè)示例代碼,代碼中有一個(gè)Winform 主線程 和 一個(gè)計(jì)數(shù)器線程,計(jì)數(shù)器線程每秒記一次數(shù),并更新界面。監(jiān)控線程檢查到非正常掛起或者死循環(huán),將在當(dāng)前目錄下寫一個(gè)Report.log 輸出監(jiān)控報(bào)告。
點(diǎn)擊Hang后主線程休眠20秒,計(jì)數(shù)器線程由于要更新界面,也同樣會被掛起。
監(jiān)控線程檢查到兩個(gè)線程掛起后報(bào)告如下:
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.IMsoComponentManager.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() |
點(diǎn)擊DeadCycle 按鈕后,讓計(jì)數(shù)器線程死循環(huán),但主線程不死循環(huán)。
監(jiān)控線程檢查到計(jì)數(shù)器線程死循環(huán)后報(bào)告如下:
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(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); } } } |
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:網(wǎng)絡(luò)轉(zhuǎn)載