自上一步以来,进程或线程已更改(Visual Studio)

15

我有一个使用SmtpClient发送电子邮件的C# .NET 控制台应用程序

我在下面的代码中遇到了以下行为:

  1. 在第35行以上设置断点
  2. 命中断点后,逐行执行第30行、32行等
  3. 在它到达第35行之前,它跳回到第28行
  4. 在某个时刻,我会收到“进程或线程自上次步骤以来已更改”的消息
  5. 跳来跳去似乎是随机的

进程或线程自上次步骤以来已更改截图

为什么我的断点会跳来跳去?

那个消息是什么意思?

我的代码有什么问题吗?

这里是Stackoverflow上另一个类似的问题。可能是相同的问题,但是它是ASP.NET)


根据Hans Passants的提示如下。

这个版本仍然允许竞态条件:在我的定时器事件处理程序中切换Timer.AutoReset属性以避免代码运行时重新进入。

private void OnTimerElapsed(object source, ElapsedEventArgs e)
{
  timer.AutoReset = false; // prevent another Elapsed event
  MyClass.SendMail(smtpServer, account, password); // do stuff
  timer.AutoReset = true; // allow another Elapsed event
}

最终版本:将计时器的AutoReset属性初始化为false,然后在Elapsed事件处理程序的结尾再次调用Timer.Start()

public void Start()
{
  timer = new Timer(checkInterval);
  timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
  timer.AutoReset = false; // prevent race condition
  timer.Start();
}

private void OnTimerElapsed(object source, ElapsedEventArgs e)
{
   bool exceptionIsNasty = false;
   try {
      MyClass.SendMail(smtpServer, account, password); // do stuff
   }
   catch (Exception ex) {
      // Log exception, set exceptionIsNasty to true if the mailing should be stopped
      //...
   }
   finally {
       if (!exceptionIsNasty) timer.Start();  // allow another Elapsed event
   }
}
3个回答

14
当多个线程执行相同的方法并且它们都遇到相同的断点时,你需要继续单步执行时,调试器无法准确地猜测应该显示哪个线程的状态。因此,你会收到一个警告,提示你正在查看不同线程的状态。本地变量值可能会有所不同。当然,执行位置也可能不同,这就是为什么高亮度发生变化的原因。
显然,这会使得调试更加困难。如果你仍在从代码中找出错误,请只启动一个线程以避免此问题。另外,一个临时的解决方法是使用“Debug + Windows + Threads”,右键单击其中一个线程并选择“Freeze”。别忘了再次取消冻结。

这个方法是static的,但我猜想即使它不是static的,每次我想发送电子邮件时都必须创建一个新的对象实例,同样的事情也会发生。此外,我猜想为什么我有多个线程执行相同的代码是因为每隔1秒就会触发一个事件,每次我慢慢按F10键,该方法都会被该事件再次调用,我的猜想正确吗? - JohnB
2
无论它是否是静态的都不相关。使用计时器调用此方法是陷入这种麻烦的好方法。如果发送电子邮件需要超过1秒钟,那么您将获得由计时器启动的另一个线程来运行该方法。非常不健康。当您单步执行该方法时,这是不可避免的,因为这当然需要超过一秒钟。请考虑每天发送八万封电子邮件的智慧。通过将计时器的AutoReset属性设置为false来避免此重新进入。 - Hans Passant
谢谢你的建议!我已经正确地实现了你的建议吗?(请参见我上面的问题更新)。顺便说一句,1秒的计时器间隔仅用于测试目的,在真实环境中,我的计时器频率要低得多。 - JohnB
不,那是不正确的,仍会导致竞争情况。在初始化计时器时将AutoReset设置为false。在Elapsed事件处理程序的末尾再次调用Start()。并留意异常,因为它们会被吞噬而没有诊断信息。 - Hans Passant
谢谢!更新后的代码现在正确吗? 模式是否在true和false之间切换Timer.Enabled - JohnB

2
如果您不能在一个线程中运行进行调试,或者不想更改某些内容来进行调试, 为了使调试更加容易,您也可以使用条件断点进行断点调试。 右键单击断点并选择“当过滤器为真 ThreadId == xxxxx”即可。
您可以在想要的方法中使用常规断点进行断点,查看当前线程ID(Threads窗口中将会有一个箭头)(调试 -> 窗口 -> 线程)。下一个断点可以按照我建议的条件设置。
请注意,不同的运行中线程ID将会发生变化,您需要在断点窗口中更新它。

0
  1. 在想要开始调试的地方设置条件断点
  2. 从“调试”>“窗口”>“线程”中选择所有线程,然后选择所有线程ctr + A,然后ctr +单击您当前正在调试的线程,右键单击并选择“冻结”。

这个答案与所选答案相同。 - HardcoreGamer

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