在Visual Studio中调试多线程程序时,“Step over”是什么意思?

29

在调试Visual Studio(我使用的是2005版本)程序时,有一件事情经常让我感到烦恼,就是当我使用“步过”(通过按下 F10 键)执行到下一行代码时,我经常会到达完全不同于我所在线程的那一行代码。这意味着我正在做的所有上下文信息都丢失了。

我该如何解决这个问题?

如果在较新版本的Visual Studio中可以做到这一点,我也想了解一下。

设置一个在下一行代码上具有条件断点以仅在此线程中断的方法不是我要寻找的答案,因为它需要太多的工作量,对我来说没有什么用处 :)

6个回答

42

我认为你的问题只有一个答案,但你认为这个方法需要做的工作太多。不过,我认为这是因为你的方法不对。让我给你介绍一下如何添加条件断点到Thread ID的步骤,这些步骤非常简单,但在你了解它们之前并不明显。

  1. 在你想要继续调试的正确线程中停止调试器(我猜通常是第一个到达的线程)。

  2. 在监视窗口输入$TID

  3. 添加带条件的断点$TID == <从监视窗口获得的$TID值>
    例如: $TID == 0x000016a0

  4. 继续执行。

$TID是Microsoft编译器(至少从Visual Studio 2003开始)的神奇变量,它具有当前线程ID的值。与查看(FS+0x18)[0x24]相比,它使事情容易多了。 =D

话虽如此,您可以通过一些简单的宏获得与调试器的单次断点相同的行为。当你单步执行时,调试器在幕后设置一个断点,运行到该断点,然后删除它。保持一致用户界面的关键是如果击中任何断点,则删除这些断点。

以下两个宏为当前线程提供了Step OverRun To Cursor。这是通过与调试器相同的方式完成的,即在执行后删除断点,无论哪个断点被击中。

您将想要为它们分配一个键组合来运行它们。

注意:一个警告--Step Over宏只有在光标位于您想要跨越的行上时才能正常工作。这是因为它通过光标位置确定当前位置,然后将行号加1。您可以尝试使用有关当前执行点的信息替换位置计算,但我无法从宏IDE中找到该信息。

下面是这些宏,祝你捉虫好运!

在Visual Studio中使用这些宏:
1.打开宏IDE(从菜单中选择:Tools->Macros->Macro IDE...
2.添加新的代码文件(从菜单中选择:Project->Add New Item...,选择Code File,然后单击Add
3.粘贴这段代码。
4.保存文件。

在Visual Studio中为运行这些宏添加键盘组合:
1.打开选项(从菜单中选择:Tools->Options
2.展开到Environment->Keyboard
3.在Show commands containing:中输入Macros.以查看您的所有宏。
4.选择一个宏,然后点击Press shortcut keys:
5.键入您想要使用的组合(backspace删除已键入的组合Imports System Imports EnvDTE Imports EnvDTE80 Imports System.Diagnostics Public Module DebugHelperFunctions Sub RunToCursorInMyThread() Dim textSelection As EnvDTE.TextSelection Dim myThread As EnvDTE.Thread Dim bp As EnvDTE.Breakpoint Dim bps As EnvDTE.Breakpoints ' For Breakpoints.Add() Dim FileName As String Dim LineNumber As Integer Dim ThreadID As String ' Get local references for ease of use myThread = DTE.Debugger.CurrentThread textSelection = DTE.ActiveDocument.Selection LineNumber = textSelection.ActivePoint.Line FileName = textSelection.DTE.ActiveDocument.FullName ThreadID = myThread.ID ' Add a "One-Shot" Breakpoint in current file on current line for current thread bps = DTE.Debugger.Breakpoints.Add("", FileName, LineNumber, 1, "$TID == " & ThreadID) ' Run to the next stop DTE.Debugger.Go(True) ' Remove our "One-Shot" Breakpoint For Each bp In bps bp.Delete() Next End Sub Sub StepOverInMyThread() Dim textSelection As EnvDTE.TextSelection Dim myThread As EnvDTE.Thread Dim bp As EnvDTE.Breakpoint Dim bps As EnvDTE.Breakpoints ' For Breakpoints.Add() Dim FileName As String Dim LineNumber As Integer Dim ThreadID As String ' Get local references for ease of use myThread = DTE.Debugger.CurrentThread textSelection = DTE.ActiveDocument.Selection LineNumber = textSelection.ActivePoint.Line FileName = textSelection.DTE.ActiveDocument.FullName ThreadID = myThread.ID LineNumber = LineNumber + 1 ' Add a "One-Shot" Breakpoint in current file on current line for current thread bps = DTE.Debugger.Breakpoints.Add("", FileName, LineNumber, 1, "$TID == " & ThreadID) ' Run to the next stop DTE.Debugger.Go(True) ' Remove our "One-Shot" Breakpoint For Each bp In bps bp.Delete() Next End Sub End Module 免责声明:我在Visual Studio 2005中编写了这些宏。你可能可以在Visual Studio 2008中正常使用它们。但在Visual Studio 2003及其之前,可能需要进行修改。


@Aaron:我正在使用VS 2008,当我在Watch窗口中输入$TID时,出现错误“名称在当前上下文中不存在”。 - akif
有人知道在Visual Studio 2012中$TID技巧是否有效吗?对我来说它不起作用,但我认为这可能是用户错误。 - Odrade
这个页面似乎表明它可以:http://msdn.microsoft.com/en-us/library/232dxah7.aspx - Aaron
1
在VS2013中,ThreadId=0x123作为断点过滤器(注意:是过滤器(Filter),而不是条件(Condition)。当你右键单击断点时,这两个菜单项是两种不同的方式来做同一件事情。) - Leo Davidson
$tid 伪变量仅在调试本机代码时可用。来源:https://msdn.microsoft.com/zh-cn/library/ms164891.aspx - Keith Morgan
显示剩余3条评论

22
你可以使用线程调试窗口(Ctrl + Alt + H)冻结不同的线程或切换到另一个线程。

谢谢。这是我迄今为止看到的最接近好解决方案的东西! - Laserallan
4
我也在做同样的事情,问题是...为什么一个简单的步骤会改变正在执行的线程? - Ignacio Soler Garcia

6

调试特定线程的简单方法是从“线程”窗口冻结所有其他线程。


5
有没有办法让VS不切换线程?在什么情况下,调试器在你跨越一行并期望到达下一行时切换到另一个线程甚至有意义呢? - stu
有没有人有这个的宏?一个插件或宏在按下stepin/over时冻结所有其他线程,跟踪该行,然后解冻所有其他线程会很好。这实际上是我想要做的,应该有一种自动化的方法。 - stu
当然,在 Visual Studio 中首次运行应该正确工作。 - stu

2

2

显然,只有在调试器之前必须中断该线程或设置了将在此线程中触发的断点时,Visual Studio 2010才会切换到其他线程,如果您按下F10

我使用以下代码来测试这种行为:

class Program
{
    static void Main(string[] args)
    {
        var t = new Thread(new ThreadStart(Work));
        t.Start();

        for (int i = 0; i < 20; i++)
        {
            Thread.Sleep(1000);
            Console.WriteLine("............");
        }

        t.Join();
    }

    static void Work()
    {
        for (int i = 0; i < 20; i++)
        {
            Thread.Sleep(1000);
            Console.WriteLine("ZZzzzzzzzzzzzzzz");
        }
    }
}

如果您刚刚启动程序或在 Main() 方法中添加断点,则按下 F10 只会逐步执行主线程中的代码。

如果您在 Work() 方法中添加断点,则调试器会同时执行两个线程。

Visual Studio 的这种行为是有意义的,但对我来说似乎都是未记录的功能......


1

最近我遇到了只调试特定线程的问题。虽然我不会否认上面的答案非常全面和有价值(希望我两天前就找到它了)- 但我实施了以下方法来帮助“日常”故障排除。

这个想法只是在多个线程中运行相同类的多个实例时的一个简单解决方法 - 这是我们的应用程序一直在做的。

我们使用唯一的ID或名称初始化所有类实例,以便知道实例是如何或为什么创建的。然后,当我们需要调试特定实例(即线程) - 而不是冻结其他线程时,我们添加以下内容:

if (instance.ID == myID)
{
    // Assert BreakPoint
}

在我们的情况下,我们建立固定的ID,这样当我们遇到问题时,我们就知道我们想要解决的ID。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接