Visual Studio 提供了多个工具和用户界面元素来帮助您调试多线程应用程序。 本教程介绍如何使用线程标记、 Parallel Stacks 窗口、 Parallel Watch 窗口、条件断点和筛选器断点。 完成本教程后,您将熟悉用于调试多线程应用程序的 Visual Studio 功能。
以下两篇文章提供了有关使用其他多线程调试工具的其他信息:
若要使用 Debug Location 工具栏和 Threads 窗口,请参阅 演练:调试多线程应用程序。
有关使用 Task (托管代码) 和并发运行时 (C++) 的示例,请参阅 演练:调试并行应用程序。 有关适用于大多数多线程应用程序类型的常规调试提示,请阅读该文章和本文。
第一步是创建多线程应用程序项目。
创建多线程应用程序项目
打开 Visual Studio 并创建新项目。
如果启动窗口未打开,请选择 “文件”>“开始”窗口。
在“开始”窗口上,选择创建新项目。
在 “创建新项目 ”窗口中,在搜索框中输入或键入 控制台 。 接下来,从语言列表中选择 C#、 C++ 或 Visual Basic ,然后从平台列表中选择 Windows 。
应用语言和平台筛选条件后,选择适用于 .NET 或 C++ 的 Console App (控制台应用程序 ) 模板,然后选择 Next (下一步)。
注释
如果未看到正确的模板,请转到 “工具>获取工具和功能...”,这将打开 Visual Studio 安装程序。 选择具有C++工作负荷的 .NET 桌面开发 或 桌面开发 ,然后选择 “修改”。
在 Configure your new project (配置新项目) 窗口中,在 Project name (项目名称) 框中键入或输入 MyThreadWalkthroughApp。 然后,选择 Next (下一步 ) 或 Create (创建),以可用选项为准。
对于 .NET Core 或 .NET 5+ 项目,请选择建议的目标框架或 .NET 8,然后选择 Create(创建)。
会出现一个新的控制台项目。 创建项目后,将显示源文件。 根据您选择的语言,源文件可能称为 Program.cs、 MyThreadWalkthroughApp.cpp 或 Module1.vb。
删除源文件中显示的代码,并将其替换为以下更新的代码。 为您的代码配置选择适当的代码段。
using System; using System.Threading; public class ServerClass { static int count = 0; // The method that will be called when the thread is started. public void InstanceMethod() { Console.WriteLine( "ServerClass.InstanceMethod is running on another thread."); int data = count++; // Pause for a moment to provide a delay to make // threads more apparent. Thread.Sleep(3000); Console.WriteLine( "The instance method called by the worker thread has ended. " + data); } } public class Simple { public static void Main() { for (int i = 0; i < 10; i++) { CreateThreads(); } } public static void CreateThreads() { ServerClass serverObject = new ServerClass(); Thread InstanceCaller = new Thread(new ThreadStart(serverObject.InstanceMethod)); // Start the thread. InstanceCaller.Start(); Console.WriteLine("The Main() thread calls this after " + "starting the new InstanceCaller thread."); } }
在“文件”菜单上,单击“全部保存”。
(仅限 Visual Basic)在 Solution Explorer(右窗格)中,右键单击项目节点,选择 Properties (属性)。 在 Application (应用程序 ) 选项卡下,将 Startup 对象 更改为 Simple (简单)。
调试多线程应用
在源代码编辑器中,查找以下代码片段:
左键单击 or for C++
std::this_thread::sleep_for
语句的Thread.Sleep
左侧装订线以插入新的断点。在装订线中,红色圆圈表示在此位置设置了断点。
在 Debug (调试 ) 菜单上,选择 Start Debugging ( F5) (启动调试) (F5)。
Visual Studio 生成解决方案,应用程序在附加调试器的情况下开始运行,然后应用程序在断点处停止。
在源代码编辑器中,找到包含断点的行。
发现线程标记
在 Debug Toolbar 中,选择 Show Threads in Source 按钮
。
按 F11 两次可前进调试器。
查看窗口左侧的槽。 在此行中,请注意一个 线程标记 图标
,它类似于两条扭曲的线程。 线程标记指示线程在此位置停止。
线程标记可以被断点部分隐藏。
将指针悬停在线程标记上。 此时将显示一个数据提示,告诉您每个已停止线程的名称和线程 ID 号。 在这种情况下,名称可能是
<noname>
。选择线程标记以查看快捷菜单上的可用选项。
查看线程位置
在 Parallel Stacks (并行堆栈) 窗口中,您可以在 Threads (线程) 视图和 (对于基于任务的编程) Tasks (任务) 视图之间切换,并且可以查看每个线程的调用堆栈信息。 在这个应用程序中,我们可以使用 Threads 视图。
通过选择 Debug WindowsParallel Stacks (调试>Windows> 并行堆栈) 打开 Parallel Stacks (并行堆栈) 窗口。 此时会看到如下所示的内容。 确切的信息可能会有所不同,具体取决于每个线程的当前位置、硬件和编程语言。
在此示例中,我们从左到右看到托管代码的以下信息:
- 当前线程(黄色箭头)已进入
ServerClass.InstanceMethod
。 您可以通过将鼠标悬停在ServerClass.InstanceMethod
上来查看线程的线程 ID 和堆栈帧。 - 线程 31724 正在等待线程 20272 拥有的锁。
- 主线程(左侧)已在 [外部代码] 上停止,如果选择 Show External Code(显示外部代码),则可以详细查看。
在此示例中,我们从左到右看到托管代码的以下信息:
- 主线程(左侧)已停止
Thread.Start
,其中停止点由线程标记图标。
- 两个线程已进入
ServerClass.InstanceMethod
,其中一个是当前线程(黄色箭头),而另一个线程已停止在Thread.Sleep
中。 - 新线程(右侧)也正在启动,但在 上
ThreadHelper.ThreadStart
停止。
- 当前线程(黄色箭头)已进入
若要在列表视图中查看线程,请选择 DebugWindows Threads(调试>Windows> 线程)。
在此视图中,您可以很容易地看到线程 20272 是主线程,当前位于外部代码中,特别是 System.Console.dll中。
注释
有关使用 Threads 窗口的更多信息,请参见 演练:调试多线程应用程序。
右键单击 Parallel Stacks 或 Threads 窗口中的项,查看快捷菜单上的可用选项。
您可以从这些右键单击菜单中执行各种作。 在本教程中,您将在 Parallel Watch (Parallel Watch ) 窗口(下一部分)中探索更多详细信息。
对变量设置监视
通过选择 Debug>Windows>Parallel WatchParallel Watch> 1 打开 Parallel Watch 窗口。
选择您看到
<Add Watch>
文本的单元格(或第四列中的空标题单元格),然后输入data
。每个线程的 data 变量的值将显示在窗口中。
选择您看到
<Add Watch>
文本的单元格(或第五列中的空标题单元格),然后输入count
。每个线程的变量值
count
将显示在窗口中。 如果您还没有看到这么多信息,请尝试按 F11 几次,以推进调试器中线程的执行。右键单击窗口中的一行以查看可用选项。
标记和取消标记线程
您可以标记线程以跟踪重要线程并忽略其他线程。
在 Parallel Watch 窗口中,按住 Shift 键并选择多行。
右键单击并选择 Flag (标志)。
将标记所有选定的线程。 现在,您可以进行筛选以仅显示已标记的线程。
在 Parallel Watch 窗口中,选择 Show Only Flagged Threads 按钮
。
列表中仅显示已标记的线程。
小窍门
标记某些线程后,可以在代码编辑器中右键单击一行代码,然后选择 Run Flagged Threads to Cursor。 确保选择所有标记的线程都将访问的代码。 Visual Studio 将暂停所选代码行上的线程,从而更容易通过 冻结和解冻线程来控制执行顺序。
再次选择 Show Only Flagged Threads 按钮,切换回 Show All Threads 模式。
要取消标记线程,请右键单击 Parallel Watch (并行监视 ) 窗口中的一个或多个已标记的线程,然后选择 Unflag (取消标记)。
冻结和解冻线程执行
小窍门
您可以冻结和解冻 (暂停和恢复) 线程,以控制线程执行工作的顺序。 这可以帮助您解决并发问题,例如死锁和争用条件。
在 Parallel Watch 窗口中,选中所有行后,右键单击并选择 Freeze。
在第二列中,每行都会显示一个暂停图标。 暂停图标表示线程已冻结。
通过仅选择一行来取消选择所有其他行。
右键单击一行,然后选择 Thaw。
暂停图标在此行上消失,这表示线程不再冻结。
切换到代码编辑器,然后按 F11。 只有未冻结的线程运行。
该应用程序还可能会实例化一些新线程。 任何新线程都不会被标记,也不会被冻结。
使用条件断点跟随单个线程
在调试器中跟踪单个线程的执行情况可能会有所帮助。 一种方法是冻结您不感兴趣的线程。 在某些情况下,您可能需要关注单个线程而不冻结其他线程,例如,重现特定 bug。 要在不冻结其他线程的情况下跟踪线程,必须避免中断除您感兴趣的线程之外的代码。 您可以通过设置 条件断点来完成此任务。
您可以根据不同的条件设置断点,例如线程名称或线程 ID。 对已知每个线程唯一的数据设置条件可能会很有帮助。 在调试期间,当您对某些特定数据值比对任何特定线程更感兴趣时,此方法很常见。
右键单击您之前创建的断点,然后选择 Conditions。
在 Breakpoint Settings (断点设置 ) 窗口中,输入
data == 5
条件表达式。小窍门
如果您对特定线程更感兴趣,请为条件使用线程名称或线程 ID。 要在 Breakpoint Settings (断点设置 ) 窗口中执行此作,请选择 Filter (筛选 ) 而不是 Conditional expression (条件表达式),然后按照筛选提示进行作。 您可能希望在应用程序代码中 命名线程 ,因为当您重新启动调试器时,线程 ID 会发生变化。
关闭 Breakpoint Settings 窗口。
选择 Restart
按钮以重新启动调试会话。
您中断了 data 变量值为 5 的线程上的代码。 在 Parallel Watch (并行监视 ) 窗口中,查找指示当前调试器上下文的黄色箭头。
现在,您可以单步执行代码 (F10) 和单步执行代码 (F11),并跟踪单个线程的执行。
只要断点条件对于线程是唯一的,并且调试器不会命中其他线程上的任何其他断点(您可能需要禁用它们),您就可以单步执行代码并单步执行代码,而无需切换到其他线程。
注释
当您推进调试器时,所有线程都将运行。 但是,调试器不会中断其他线程上的代码,除非其他线程之一遇到断点。