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
。
提交和查看相关反馈