代码改变世界

C# 线程手册 第六章 线程调试与跟踪

2012-04-21 16:41  DanielWise  阅读(15923)  评论(0编辑  收藏  举报

  调试与跟踪是两个经常使用到的技术,这两种技术对程序员是非常必要的。前者允许开发人员分析一个程序的变量值以及代码,一步一步跟踪代码流程。后者允许我们跟踪我们应用程序的行为,在一个监听器(一个日志文件,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 应用程序来说,将会出现下面的对话框:

2012-4-21 16-23-43

  在编译一个Debug 工程时,输出目将包含exe/dll 和一个pdb(程序数据库) 文件。因为IL 在数组中存储参数值和私有成员值,所以这些变量的原始名字都不存在了 - 被替换成一些与调试相关的其他信息。当编译一个工程来调试时,或者在命令行编译中使用/debug:full 参数,都会在编译过程中生成一个pdb 文件。exe/dll 文件包含一个指向pdb 文件的绝对路径,如果调试器找不到程序数据库文件,它就开始在程序的当前路径以及属性对话框配置页设置的路径中寻找。如果最后调试器找不到一个pdf 文件,那么它会重新生成一个。

调试器窗口

  一旦你已经在Visual Studio.NET 中加载你的工程,你就可以通过运行它来对其调试,等到代码运行到断点处,然后使用F10/F11 键来跳过/进入方法调用。如果你不是使用发布版本,你将看到IDE显示很多窗口。在调试期间这些窗口将填充很多变量值,对象列表,调用堆栈等等。让我们更加仔细地检查这些调试工具,并看看如何使用它们来帮助调试多线程应用程序。

本地窗口

  这个窗口允许你检查并修改你正在调试的方法中定义的本地变量的值(包括方法参数的值)。例如,调试上一篇博客中ThreadPoolManager的Main() 方法,你将看到两个变量的内容:tp 和 p, 如下截图所示:

2012-4-21 16-25-53

  你可以通过Debug->Window->Local 菜单激活这个窗口,你也可以通过Ctrl + Alt +V 激活这个窗口。

查看窗口

  你可以从源代码中抓取变量并把它们扔到Watch 窗口来监测它们的值和结构。在下面的截图中,上图中的tp对象被放入了查看窗口中。

2012-4-21 16-27-57

  你可以点击+号来展开树节点,然后检查并改变属性值。你可以通过Ctrl + Alt + W 激活窗口。

  你可以从源代码中选择一个变量然后右键选择查看来监测变量值。

常用窗口 - 立即窗口

  这个窗口提供一个文本输入框,你可以查询一个变量内容并改变变量值。当你需要收集变量内容时你需要使用在表达式之前加一个问好。在下面的截图中,我们检查了tp对象的Debug属性,把它设置为false, 然后显示它的值。

2012-4-21 16-30-48

  除了以上功能,这个窗口还允许你使用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键来去除断点。

  你可以在断点窗口管理设置的所有断点。

  2012-4-21 16-32-52

  使用这个窗口,你可以添加一个新断点,删除一个或者所有断点,禁用所有断点,添加并移除窗口指定列并查看断点属性。

  通过断点,你可以中断一个线程的执行并检查它当前的上下文内容。

  从管理断点窗口中选择断点属性,将会显示一个新的对话框,在这个对话框中你可以设置仅当一个特定变量改变它的内容时才激活一个断点。你需要在断点属性页中确定变量的名字。这个功能在多线程场景中很有用,因为当一些不可预期的错误发生时你可以察觉。

  最后,管理断点窗口中的Hit 数量对话框允许开发人员仅当一个断点到达特定触发数量时才开启。这个功能在多线程应用程序中也很有用,因为它能帮助你查看多久生成一个线程。

2012-4-21 16-35-18

  从下拉列表中,你可以选择与断点关联的条件。例如,你可以在将要退出循环之前激活断点。你可以选择那些触发次数等于设置值的断点。

  为了在到达断点之前执行所有代码你需要使用F5键。

调试线程

  Visual Studio.NET 调试器提供了一个特别的窗口来在调试期间管理线程。你可以通过Debug->Windows->Thread 或者Ctrl + Alt + H 来显示这个窗口。

  2012-4-21 16-36-09

  线程窗口包含以下列:

2012-4-21 16-42-27

  你可以在线程窗口中通过简单地双击线程组件来在不同线程间切换。此外,在一个线程上右键,你可以选择冻结菜单来暂停线程执行。你可以选择Thaw 菜单来恢复冻结的线程状态。

 

下一篇介绍代码跟踪技术…