添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Visual Studio 提供多种工具和用户界面元素,用于调试多线程应用程序。 本教程演示如何使用线程标记、“并行堆栈” 窗口、“并行监视” 窗口、条件断点、筛选器断点。 完成本教程可使你熟悉用于调试多线程应用程序的 Visual Studio 功能。

下面两篇文章额外介绍了如何使用其他多线程调试工具:

  • 若要使用“调试位置” 工具栏和“线程” 窗口,请参阅 演练:调试多线程应用程序

  • 有关使用 Task (托管代码)和并发运行时 (C++) 的示例,请参阅 演练:调试并行应用程序 。 有关适用于大多数多线程应用程序类型的常规调试技巧,请阅读该文章和本文章。

    第一步是创建多线程应用程序项目。

    创建一个多线程应用项目

  • 打开 Visual Studio 并创建一个新项目。

    如果“开始”窗口未打开,请选择“文件”>“启动窗口”。

    在“开始”窗口上,选择“创建新项目”。

    在“创建新项目”窗口的搜索框中输入或键入“控制台” 。 接下来,从“语言”列表中选择“C#”、“C++”或“Visual Basic”,然后从“平台”列表中选择“Windows” 。

    应用语言和平台筛选器之后,对 .NET 或 C++ 选择“ 控制台应用 ”模板,然后选择“ 下一步 ”。

    如果没有看到正确的模板,请转到“工具”>“获取工具和功能...”,这会打开 Visual Studio 安装程序 。 选择“.NET 桌面开发”或“使用 C++ 的桌面开发”工作负载,然后选择“修改” 。

    在“配置新项目”窗口中,在“项目名称”框中键入或输入 MyThreadWalkthroughApp。 然后,选择“下一步”或“创建”,(视具体提供的选项而定)。

    对于 .NET Core 或 .NET 5+ 项目,选择建议的目标框架或 .NET 8,然后选择“创建”

    新的控制台项目随即显示。 创建该项目后,将显示源文件。 根据所选语言,源文件名称可能是 Program.cs、MyThreadWalkthroughApp.cpp 或 Module1.vb 。

  • 删除源文件中显示的代码,并将其替换为以下更新的代码。 为代码配置选择适当的代码片段。

    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."); Public Class ServerClass ' The method that will be called when the thread is started. Public count = 0 Public Sub InstanceMethod() Console.WriteLine( "ServerClass.InstanceMethod is running on another thread.") Dim data = count + 1 ' 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) End Sub End Class Public Class Simple Public Shared Sub Main() Dim ts As New ThreadStarter For index = 1 To 10 ts.CreateThreads() End Sub End Class Public Class ThreadStarter Public Sub CreateThreads() Dim serverObject As New ServerClass() ' Create the thread object, passing in the ' serverObject.InstanceMethod method using a ' ThreadStart delegate. Dim InstanceCaller As New Thread(AddressOf serverObject.InstanceMethod) ' Start the thread. InstanceCaller.Start() Console.WriteLine("The Main() thread calls this after " _ + "starting the new InstanceCaller thread.") End Sub End Class void doSomeWork() { std::cout << "The doSomeWork function is running on another thread." << std::endl; int data = count++; // Pause for a moment to provide a delay to make // threads more apparent. std::this_thread::sleep_for(std::chrono::seconds(3)); std::string str = std::to_string(data); std::cout << "The function called by the worker thread has ended. " + str<< std::endl; int main() { std::vector<std::thread> threads; for (int i = 0; i < 10; ++i) { threads.push_back(std::thread(doSomeWork)); std::cout << "The Main() thread calls this after starting the new thread" << std::endl; for (auto& thread : threads) { thread.join(); return 0;
  • 在“文件” 菜单上,单击“全部保存” 。

  • (仅适用于 Visual Basic)在“解决方案资源管理器”(右窗格)中,右键单击项目节点,然后选择“属性” 。 在“应用程序” 选项卡下,将“启动对象” 更改为“简单” 。

    调试多线程应用

  • 在源代码编辑器中,查找以下代码片段:

  • Thread.Sleep 语句或 std::this_thread::sleep_for 语句(针对 C++)的左滚动条槽中单击左键,以插入新的断点。

    在滚动条槽中,红色圆圈表示已在该位置设置了一个断点。

  • 在“调试” 菜单上,单击“开始调试(F5)” 。

    Visual Studio 将生成该解决方案,应用在附加了调试器的情况下开始运行,然后在断点处停止。

  • 在源代码编辑器中,找到包含断点的行。

    发现线程标记

  • 在调试工具栏中,单击“在源中显示线程”按钮 在源中显示线程

  • F11 两次以完成调试器的下一步。

  • 查看窗口左侧的滚动条槽。 在此行中,注意“线程标记”图标 线程标记 ,类似于一条双绞线 。 线程标记指示线程在此位置停止。

    线程标记可以被断点部分隐藏。

  • 将指针悬停在线程标记上。 此时会出现一个数据提示,告知你每个已停止线程的名称和线程 ID 号。 在这种情况下,名称可能是 <noname>

  • 选择线程标记,以查看快捷菜单上的可用选项。

    查看线程位置

    在“并行堆栈” 窗口中,可以在“线程”视图和“任务”视图(适用于基于任务的编程)之间进行切换,并且可以查看每个线程的调用堆栈信息。 在此应用中,我们可以使用“线程”视图。

  • 通过选择“调试”>“窗口”>“并行堆栈” ,打开“并行堆栈” 窗口。 此时会看到如下所示的内容。 确切信息可能因每个线程的当前位置、硬件和编程语言而异。

    在此示例中,从左到右会看到托管代码的以下信息:

  • 当前线程(黄色箭头)已进入 ServerClass.InstanceMethod 。 可以通过将鼠标悬停在 ServerClass.InstanceMethod 上来查看线程的线程 ID 和堆栈帧。
  • 线程 31724 正在等待线程 20272 拥有的锁。
  • 主线程(左侧)已在[外部代码]上停止,如果选择“ 显示外部代码 ”,则可以详细查看这些代码。
  • 主线程(左侧)已在 Thread.Start 处停止,停止点由线程标记图标 线程标记 标识。
  • 两个线程已进入 ServerClass.InstanceMethod ,其中一个线程是当前线程(黄色箭头),另一个线程已停止在 Thread.Sleep 中。
  • 新线程(右侧)也已启动,但是停止在 ThreadHelper.ThreadStart 上。
  • 若要在列表视图中查看线程,请选择“ 调试 ”>“ Windows ”>“ 线程 ”。

    在此视图中,可以轻松看到线程 20272 是主线程,当前位于外部代码中,特别是 System.Console.dll

    有关使用“ 线程 ”窗口的详细信息,请参阅 演练:调试多线程应用程序

  • 右键单击“ 并行堆栈 ”或“ 线程 ”窗口中的条目,查看快捷菜单上的可用选项。

    可以从这些右键单击菜单中执行各种操作。 对于本教程,在“并行监视”窗口(后续各节)中详细探索这些详细信息

    对变量设置监视

  • 通过选择“调试” >“窗口” >“并行监视” >“并行监视 1” ,打开“并行监视”窗口 。

  • 选择 <Add Watch> 文本所在的单元格(或第 4 列中的空标头单元格),输入 data

    窗口中会显示每个线程的数据变量的值。

  • 选择 <Add Watch> 文本所在的单元格(或第 5 列中的空标头单元格),输入 count

    窗口中会显示每个线程的 count 变量的值。 如果看不到这么多的信息,请尝试按 F11 几次以继续在调试器中执行线程。

  • 右键单击窗口中的某一行以查看可用选项。

    标记线程和取消标记线程

    可以通过标记线程来追踪重要的线程,并忽略其它线程。

  • 在“并行监视” 窗口中,按住 Shift 键并选择多行。

  • 右键单击并选择“标记” 。

    系统会标记所有选定的线程。 现在,可以进行筛选以仅显示标记的线程。

  • 在“并行监视”窗口中,选择“仅显示标记的线程”按钮 显示标记的线程

    列表中仅显示标记的线程。

    在标记一些线程后,可以右键单击代码编辑器中的代码行,然后选择“将标记的线程运行到光标处” 。 请确保选择所有已标记的线程将达到的代码。 Visual Studio 将在选择的代码行处暂停线程,这样就可以通过 冻结和解冻线程 更容易地控制执行顺序。

  • 再次选择“仅显示标记的线程” 按钮,以切换回“显示全部线程” 模式。

  • 若要取消标记线程,请在“并行监视” 窗口右键单击一个或多个已标记线程,然后选择“取消标记” 。

    冻结和解冻线程执行

    可以通过冻结和解冻(暂停和恢复)线程来控制线程执行工作的顺序。 这有助于解决并发问题,例如死锁和争用条件。

  • 在“并行监视” 窗口中,在选中所有行的情况下,右键单击并选择“冻结” 。

    在第二个列中,每个行出现一个暂停图标。 暂停图标指示该线程已冻结。

  • 仅选择一行,取消选中其他行。

  • 右键单击某行并选择“解冻” 。

    暂停图标在此行上消失,表明线程已不再被冻结。

  • 切换到代码编辑器,按 F11 。 仅运行未冻结的线程。

    应用可能还实例化某些新线程。 任何新线程均处于未标记状态,不会被冻结。

    使用条件断点跟踪单个线程

    可以在调试器中对单线程的执行情况进行跟踪。 一种方法是冻结不感兴趣的线程。 在某些情况下,可能需要在不冻结其它线程的情况下跟踪单个线程,例如重现特定 Bug。 若要在不冻结其他线程的情况下跟踪某个线程,必须避免在不感兴趣的线程上中断。 可通过设置 条件断点 来执行此任务。

    可以根据不同的条件(例如,线程名称或线程 ID)来设置断点。 对于已知对每个线程唯一的数据,设置该条件会有所帮助。 当你对某些特定数据值比对任何特定线程更感兴趣时,这种方法在调试过程中很常见。

  • 右键单击先前创建的断点,然后选择“条件” 。

  • 在“断点设置” 窗口中,输入 data == 5 作为条件表达式。

    如果对特定的线程更感兴趣,则请使用线程名称或线程 ID 作为条件。 若要在“断点设置” 窗口中执行此操作,请选择“筛选器” 而不是“条件表达式” ,并按照筛选器提示操作。 可能需要在应用代码中 指定线程名称 ,因为线程 ID 在重启调试程序时会更改。

  • 关闭“断点设置” 窗口。

  • 选择重启 重启应用 按钮以重启调试会话。

    在数据变量的值为 5 的线程中,中断代码执行。 请在“并行监视” 窗口中,寻找表示当前调试器上下文的黄色箭头。

  • 现在,可以逐过程执行代码 ( F10 ) 和单步执行代码 ( F11 ),并跟踪单个线程的执行情况。

    只要断点条件对于线程是唯一的,并且调试器不会在其他线程上命中其他任何断点(你可能需要禁用它们),你就可以逐过程执行代码和单步执行代码,而无需切换到其他线程。

    调试器前进时,所有线程都将运行。 但是,调试器不会中断其他线程上的代码,除非其中一个线程命中断点。

  • 调试多线程应用
  • 如何:在调试时切换到另一个线程
  • 如何:使用“并行堆栈”窗口
  • 如何:使用“并行监视”窗口
  • 即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅: https://aka.ms/ContentUserFeedback

    提交和查看相关反馈