調試與跟蹤是兩個經常使用到的技術,這兩種技術對程序員是非常必要的。前者允許開發人員分析一個程序的變量值以及代碼,一步一步跟蹤代碼流程。后者允許我們跟蹤我們應用程序的行為,在一個監聽器(一個日志文件,Windows 事件日志等)中顯示信息。它們是創建魯棒性應用程序的基礎,因為它們給開發人員提供了一個簡單的方式去跟蹤並理解我們的應用程序是如何工作的。這兩種技術最大的不同是跟蹤可以在一個程序運行過程中完成,而調試用於設計階段以及我們的最終版程序發布之前。
桌面應用程序開發人員通常有出色的調試技術支持,能夠使用斷點去檢查變量內容。.NET 在這方面也不例外,但是在一個多線程應用程序中使用斷點來觀察一些情況是一個固有的問題,在這章我們將關注這個問題。
除了桌面應用程序,開發人員一直以來都苦於沒有一個好的調試器來跟蹤諸如ASP.NET 等Web 應用程序。為了獲知一個變量的值或者代碼流程(這些通常由調試器處理),ASP 開發人員不得不使用Response.Write() 語句來跟蹤代碼,顯示一些諸如進入函數,退出循環之類的消息。然后在他們測試完ASP應用程序之后,他們還需要把這些不需要的語句去掉。這不是調試一個程序的最好的方式。
幸運的是,NET 為下一代的ASP 開發人員帶來了調試功能並提供了四個有用的類:Trace, Debug, BooleanSwitch 以及 TraceSwitch. 除此之外,任何.NET 語言都可以使用這些類,所以每個選擇使用Visual Studio .NET 來創建應用程序的開發人員都可以使用Visual Studio.NET 提供的工具調試代碼。
多種跟蹤與調試技術對使用線程的應用程序來說非常重要。如果功能實現的很好的話,這些技術允許開發人員跟蹤每個線程的行為,發現任何應用程序異常,比如一個未知的資源消耗、搶奪bugs 等等。
在這章,我們將按照以下順序同時分析跟蹤和調試技術:
1. 使用Visual Studio .NET 調試分析器以及它的強大工具;
2. 使用.NET 跟蹤類來在我們的代碼中實現這個特性;
3. 創建一個能夠使用所有這些跟蹤技術/工具的程序。
在這章,Visual Studio.NET 對於使用盡可能多的跟蹤和調試技術來說是很必要的,通過/d:TRACE=TRUE 開關,一些跟蹤功能可以使用命名行實現。
創建應用程序
通常,當你創建一個程序(或者程序的一部分)時,你首先實現代碼部分然后嘗試運行程序。有時候程序按照你期望的那樣運行;但是通常來說並不是這樣。當程序不按照我們期望的運行時,你通過仔細地檢查你寫的代碼來嘗試發現究竟發生了什么。在Visual Studio..NET 中,你可以使用調試器添加幾個斷點並讓程序執行停在邪惡(可能出錯的,以下類同)方法附近或者之前,然后一步步調試到邪惡方法內部,檢查變量值並精確地了解哪里出錯了。最后,當所有功能都正常以后你可以創建一個發布版本(一個沒有任何調試工具使用的變量的版本),並把它分發出去。
在這種類型的應用程序中,在開發過程中,你可以插入跟蹤代碼。事實上,即便程序目前工作地非常好,總會有一條case (尤其是當一些外部的、第三方的組件失敗時)還沒有被發現。在這種情況下,如果你在代碼里加了跟蹤指令,你可以打開跟蹤並檢查結果日志來了解可能發生了什么事情。此外,跟蹤功能是一個發現程序執行任務時哪里消耗資源多或者哪里花費太長時間的利器。在使用線程的應用程序中,你應該使用跟蹤功能,否則的話觀察每個線程的行為、確定跟蹤條件、發現潛在的死鎖條件或者時間消耗問題會變得很困難。
跟蹤,調試以及性能檢測技術通常被稱作儀器儀表。這是指能夠監測一個程序的性能、行為並能診斷錯誤和問題的能力。所以,一個支持儀器儀表功能的程序應該包括:
1. 調試:在開發過程中解決錯誤和問題;
2. 代碼跟蹤:在程序執行過程中在一個監聽程序中接收信息;
3. 性能計數器:監控一個程序性能
讓我們通過在我們的程序中添加一些指令來檢查.NET Framework 給我們提供了什么。
調試代碼
通常,當你測試你的程序來查看它的行為是否為你所期待的那樣時,你開始仔細地檢查你所寫的代碼。如果你使用Visual Studio.NET 類創建你的程序,它會提供很多出色的工具來調試你的程序。此外,無論你使用什么語言開發程序,你都將使用同樣的調試器和工具。更狠的是,基礎調試器功能由Visual Basic 6 和 Visual C++ IDEs 繼承下來的,這意味着大多數開發人員都應該比較熟悉。然而,我們將不會花費太多時間在調試器上,我們主要關注與線程相關的功能。
新的調試器提供以下功能:
1. 使用同樣的工具調試由不同語言寫的不同應用程序,並支持由混合語言(C# 中嵌入C++)寫成的應用程序;
2. 調試SQL Server 存儲過程;
3. 調試.NET Framework 和Win32 原生代碼,假設你在調試Visual C#.NET 應用程序而你的線程使用了一個COM+組件,你可以使用同樣的調試器同時調試這兩個程序;
4. 一個更加強大的遠程調試器
如果你曾經使用過Visual Basic 6 調試器,你將知道它之中的一些功能在新調試器中已經被刪除了。比如在調試過程中改變代碼然后繼續執行的功能。如果使用Visual Studio.NET 調試器,那么你會發現這個功能已經不存在了,因為對代碼的每次修改都要求重新編譯一次(x64 程序不允許修改,x86 程序是允許的)。
在本書的這部分,我們將分析由Visual Studio.NET IDE 提供的調試工具,它對多線程應用程序開發過程的測試和發現錯誤是非常有用的。
Visual Studio.NET 調試器
使用Visual Studio .NET 調試器可以簡單地在你希望檢查的代碼位置插入一個斷點來中斷程序的執行。當你的程序被阻塞以后,調試器提供了很多工具來檢查和編輯變量內容,檢查內存和調用堆棧等等。
設置調試器參數
為了使用Visual Studio.NET 調試器,你不得不使用Debug 模式編譯程序。在那種方式,除了實現代碼你還可以添加一些額外的調試信息。當所有工作看起來很正常時,你將在重編譯代碼后發布程序,選擇Release 模式,這種模式會移除所有的調試信息。
當一個新的調試過程開始后,很多資源被加載如內存。事實上,調試器把變量代碼填充到內存中以允許我們調試非托管代碼,SQL Server 存儲過程等等。當你不需要調試非托管代碼時將這些特性去除是一個很好的主意。你可以通過Solution Explorer->選中工程->右鍵選擇屬性->屬性頁對話框 來修改調試器設置。對一個Windows 應用程序來說,將會出現下面的對話框:
在編譯一個Debug 工程時,輸出目將包含exe/dll 和一個pdb(程序數據庫) 文件。因為IL 在數組中存儲參數值和私有成員值,所以這些變量的原始名字都不存在了 - 被替換成一些與調試相關的其他信息。當編譯一個工程來調試時,或者在命令行編譯中使用/debug:full 參數,都會在編譯過程中生成一個pdb 文件。exe/dll 文件包含一個指向pdb 文件的絕對路徑,如果調試器找不到程序數據庫文件,它就開始在程序的當前路徑以及屬性對話框配置頁設置的路徑中尋找。如果最后調試器找不到一個pdf 文件,那么它會重新生成一個。
調試器窗口
一旦你已經在Visual Studio.NET 中加載你的工程,你就可以通過運行它來對其調試,等到代碼運行到斷點處,然后使用F10/F11 鍵來跳過/進入方法調用。如果你不是使用發布版本,你將看到IDE顯示很多窗口。在調試期間這些窗口將填充很多變量值,對象列表,調用堆棧等等。讓我們更加仔細地檢查這些調試工具,並看看如何使用它們來幫助調試多線程應用程序。
本地窗口
這個窗口允許你檢查並修改你正在調試的方法中定義的本地變量的值(包括方法參數的值)。例如,調試上一篇博客中ThreadPoolManager的Main() 方法,你將看到兩個變量的內容:tp 和 p, 如下截圖所示:
你可以通過Debug->Window->Local 菜單激活這個窗口,你也可以通過Ctrl + Alt +V 激活這個窗口。
查看窗口
你可以從源代碼中抓取變量並把它們扔到Watch 窗口來監測它們的值和結構。在下面的截圖中,上圖中的tp對象被放入了查看窗口中。
你可以點擊+號來展開樹節點,然后檢查並改變屬性值。你可以通過Ctrl + Alt + W 激活窗口。
你可以從源代碼中選擇一個變量然后右鍵選擇查看來監測變量值。
常用窗口 - 立即窗口
這個窗口提供一個文本輸入框,你可以查詢一個變量內容並改變變量值。當你需要收集變量內容時你需要使用在表達式之前加一個問好。在下面的截圖中,我們檢查了tp對象的Debug屬性,把它設置為false, 然后顯示它的值。
除了以上功能,這個窗口還允許你使用IDE命令,比如創建一個文件或者工程,尋找一個字符串或者任何你可以通過Visual Studio.NET 實現的操作。你可以使用>cmd 命令將立即窗口切換成命令模式。在命令模式中,你將得到IDE 的職能感知功能來獲得需要的命令。你可以通過>immed 命令切換回來。
單步調試
我們已經簡單介紹了調試窗口的幾個有用工具,現在可以專注代碼方面。Visual Studio.NET 調試器允許開發人員在一行和多行代碼間單步調試,在運行時查看程序行為。此外,你可以調試非托管代碼和SQL Server 存儲過程。調試器提供了三種不同的方式來單步調試代碼:
1. Step Into: 按F11鍵你將一次執行一步代碼,進入調試過程中遇到的每個函數的函數體(僅當源代碼和調試參數存在時);
2. Step Over: 按F10鍵你將一次執行一個函數而不會進入函數體內部(按照一行代碼來執行一個函數);
3. Step Out: 按Shift + F11 鍵你將執行當前方法中所有的剩余代碼,並到達調用這個函數的方法的下一行
每次你通過這些按鍵單步調試到下一行時,你都在執行當前高亮的代碼。
Visual Studio.NET 調試器提供的另外一個有用的功能是運行到光標。你可以通過這個功能執行高亮代碼行和光標所在代碼行之間的所有代碼。
最后,Visual Studio.NET 調試器提供了一個方式來改變我們程序的執行點。你可以通過運行調試器並在菜單中設置下一條語句來決定移動程序的執行點。要小心使用這個特性,因為原來位置和新位置之前的每行代碼都可能出錯。
設置斷點
在大的源代碼應用程序中,在到達你感興趣的方法之前單步調試之前的所有代碼是不現實的。調試器提供在代碼中設置斷點的功能。功能如其名,一個斷點是你的程序必須停止的點。你可以在開始調試之前設置斷點,簡單地把光標放置在需要設定斷點的代碼位置並按F9鍵 - 或者在左邊界面點擊鼠標左鍵。一個紅色高亮標志將會在你想要代碼停止的地方顯示出來,同時會在代碼主窗體的左邊欄添加一個紅色圓點。你可以通過去掉紅色圓點或者再次點擊F9鍵來去除斷點。
你可以在斷點窗口管理設置的所有斷點。
使用這個窗口,你可以添加一個新斷點,刪除一個或者所有斷點,禁用所有斷點,添加並移除窗口指定列並查看斷點屬性。
通過斷點,你可以中斷一個線程的執行並檢查它當前的上下文內容。
從管理斷點窗口中選擇斷點屬性,將會顯示一個新的對話框,在這個對話框中你可以設置僅當一個特定變量改變它的內容時才激活一個斷點。你需要在斷點屬性頁中確定變量的名字。這個功能在多線程場景中很有用,因為當一些不可預期的錯誤發生時你可以察覺。
最后,管理斷點窗口中的Hit 數量對話框允許開發人員僅當一個斷點到達特定觸發數量時才開啟。這個功能在多線程應用程序中也很有用,因為它能幫助你查看多久生成一個線程。
從下拉列表中,你可以選擇與斷點關聯的條件。例如,你可以在將要退出循環之前激活斷點。你可以選擇那些觸發次數等於設置值的斷點。
為了在到達斷點之前執行所有代碼你需要使用F5鍵。
調試線程
Visual Studio.NET 調試器提供了一個特別的窗口來在調試期間管理線程。你可以通過Debug->Windows->Thread 或者Ctrl + Alt + H 來顯示這個窗口。
線程窗口包含以下列:
你可以在線程窗口中通過簡單地雙擊線程組件來在不同線程間切換。此外,在一個線程上右鍵,你可以選擇凍結菜單來暫停線程執行。你可以選擇Thaw 菜單來恢復凍結的線程狀態。
下一篇介紹代碼跟蹤技術…








