当调试器暂停代码时,如何忽略时间的流逝?

3

我正在进行一些计算,如果运行时间太长,我就需要设置一个超时时间。我可能会设置5秒的超时时间,并在我的代码中定期轮询。实际的代码更加复杂,有很多递归和分散在许多类中,但这应该可以给出它是如何工作的一个大致的概念。基本上,每当某个东西调用递归或执行可能需要时间的操作时,它都会调用AssertTimeout()。

private DateTime endTime;
public void PerpareTimeout(int timeoutMilliseconds)
{
    endTime = DateTime.UtcNow.AddMilliseconds(timeoutMilliseconds);
}

public void AssertTimeout()
{
    if (DateTime.UtcNow > endTime)
        throw new TimeoutException();
}

public void DoWork1()
{
    foreach (var item in workItems)
    {
        AssertTimeout();
        DoWork2(item)
    }
}
public void DoWork2(WorkItem item)
{
    foreach (var item in workItems)
    {
        AssertTimeout();
        // ...
    }
}

当我附加了调试器并暂停执行时,问题就出现了。我希望在暂停的时间内能够禁用超时。因此,如果它运行了2秒钟,我触发了断点并等待了5分钟,然后继续执行,它会在恢复执行后再运行3秒钟,然后超时。

我可以使用类似这样的方法:

public void PrepareTimeout(int timeoutMilliseconds)
{
    if (System.Diagnostics.Debugger.IsDebuggerAttached)
        endTime = DateTime.MaxValue;
    else
        endTime = DateTime.UtcNow.AddMilliseconds(timeoutMilliseconds);
}

但是在调试环境下运行程序时,这基本上相当于给予无限超时时间,而我希望在不暂停程序的情况下正常超时。

有没有办法测量经过的时间,而不计算调试器暂停的时间?

3个回答

3
在看了Matias Cicero的回答后,我有了一个想法:如果调试器已经连接,那就启动一个什么也不做只睡觉的守护线程。也许以100毫秒为单位。如果程序变成暂停状态,Thread.Sleep的调用将比预期时间更长,因此它可以通过差异增加结束时间。
public class DebugWatchDog : IDisposable
{
    private bool Terminated;
    private Action<DateTime> updateEndTime;
    private DateTime endTime;
    public DebugWatchDog(Action<DateTime> updateEndTime, DateTime endTime)
    {
        if (!System.Diagnostics.Debugger.IsDebuggerAttached)
            return;
        this.updateEndTime = updateEndTime;
        this.endTime = endTime;
        updateEndTime(DateTime.MaxValue);
        var thread = new Thread(Watch);
        thread.Start();
    }
    public void Dispose()
    {
        lock (this)
            Terminated = true;
    }
    private void Watch()
    {
        DateTime priorTime = DateTime.UtcNow;
        while (true)
        {
            lock (this)
                if (Terminated)
                    return;
            Thread.Sleep(100);
            DateTime nextTime = DateTime.UtcNow;
            var diff = nextTime - priorTime;
            if (diff.TotalMilliseconds > 115)
            {
                endTime += diff;
            }
            if (DateTime.UtcNow > endTime)
            {
                updateEndTime(endTime);
                return;
            }
            priorTime = nextTime;
        }
    }
}

2

有一个不太好的解决方法。

你需要使用Stopwatch,而非计算当前时间和开始时间之间的差值。

比如,你有以下代码:

Stopwatch watch = Stopwatch.StartNew();

DoSomething();
DoAnotherThing(); <-- You want to add a breakpoint here
DoFinalThings();

watch.Stop();

在你想要设置断点的那一行之前添加三行代码即可实现您的目标:
Stopwatch watch = Stopwatch.StartNew();

DoSomething();

watch.Stop();       // Stops measuring time
Debugger.Break();   // Programatically breaks the program at this point
watch.Start();      // Resumes measuring time

DoAnotherThing();
DoFinalThings();

watch.Stop();

有趣的想法,但实际上并不切实际。这样做的话,逐行步进、进入/退出函数等都将完全不可行。在非常特定的情况下,我可能会找到它的用处,所以我会把它作为一种可能性记在心里。 - Bryce Wagner
实际上,您的答案启发了我另一种可能的解决方法,我将其发布为一个答案。 - Bryce Wagner

1
理想情况下,当代码暂停时,您希望触发一些事件。由于调试器不会触发事件,因此需要一个解决方法,正如Matias所提到的那样。他的解决方案要求您知道应该何时启动/停止秒表,并在此基础上进行改进。这种解决方案的缺点是它需要以足够高的分辨率进行常量轮询以满足您的需求(这可能对应用程序的其余部分产生负面影响,从而影响您尝试从计时器中检索的任何数字)。最终目标是我们将进行以下计算:"运行时间已暂停" = "总共花费的时间" - "总共暂停的时间"。我们将尝试跟踪程序上次活动运行的时间,并找出程序从调试中恢复后下次主动运行的时间。
public static class ProgramTimer
{
    public static int resolution = 1000; //resolution of time needed in ms
    private static System.Diagnostics.Stopwatch stopwatch;
    private static System.Timers.Timer pollingTimer;
    private static System.TimeSpan lastMeasuredDebugTimespan;

    public static void Start()
    {
        pollingTimer = new System.Timers.Timer();
        pollingTimer.Interval = resolution;
        pollingTimer.Elapsed += timerEvent;
    }

    private static void timerEvent()
    {
        if (System.Diagnostics.Debugger.IsDebuggerAttached) {
            if (stopwatch == null) {
                stopwatch = System.Diagnostics.Stopwatch.StartNew();
            }
        } else {
            if (stopwatch != null) {
                stopwatch.Stop();
                lastMeasuredDebugTime = stopwatch.Elapsed.TotalMilliseconds;
                stopwatch = null;
            }
        }
    }

    public static System.TimeSpan getTimespanInDebug()
    {
        if (lastMeasuredDebugTimespan) {
            return lastMeasuredDebugTimespan;
        }
        return null;
    }
}

PrepareTimeout中,您需要调用:ProgramTimer.Start() 而在AssertTimeout中:
public void AssertTimeout()
{
    DateTime newEndTime;
    newEndTime = endTime; 
    if (ProgramTimer.getTimespanInDebug()) {
        newEndTime = endTime.Subtract(ProgramTimer.getTimespanInDebug());
    }
    if (DateTime.UtcNow > newEndTime)
        throw new TimeoutException();
}

希望这能帮到您!

谢谢,Matias发布他的答案之后,我也想到了一个非常类似的想法。我的解决方案让AssertTimeout()函数变得更简单(因为它被频繁调用),但它们基本上实现了相同的功能:测量预期的时间和实际的时间之间的差异,并通过该差异延长结束时间。 - Bryce Wagner

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