【问题标题】:C# unhandled exception in try / catch block, detecting a freezeC# 在 try / catch 块中未处理的异常,检测到冻结
【发布时间】:2017-03-21 12:16:56
【问题描述】:

目标:给未测试的外部代码一个超时。如果我的应用程序会导致对方死机,我想立即知道。假设这是非常复杂的单元测试。如果测试客户端使用了我的应用并在 500 毫秒内返回 - 它通过了,如果没有 - 它失败了。

所以是的,我不会对客户温柔,这是一个碰撞测试站点。我等待 500 毫秒,然后我不在乎客户端还在等待什么,我想立即将其崩溃并引发异常,但然后将其报告为测试结果。

可能是众多测试结果之一。

所以,现在,在这里,代码:

    Console.Write("Testing");
    try {
        for (int j = 0; j < 3; j++) {
            Console.Write(".");
            for (int i = 0; i < 256; i++) {
                using (var cts = new CancellationTokenSource(500)) {
                    var token = cts.Token;
                    token.Register(() => throw new TimeoutException("FREEZE DETECTED"));
                    clients.Add(LDSCheckAsync().Result);
                }
            }
            clients.ForEach(i => i.Dispose());
            clients.Clear();
        }
        Console.WriteLine("OK.");
    }
    catch {
        clients.ForEach(i => i?.Dispose());
        Console.WriteLine("FAIL!");
    }

我设计的测试失败了。但我没有收到“失败”消息,我收到未处理的异常。

为什么,以及如何正确地做到这一点。在你尝试写评论之前,请考虑一下我不能在这里使用IfCancellationRequested 或类似的东西。被测函数通常会立即退出,但由于某些网络错误,它可能会进入 30 秒冻结。我完全不知道它何时发生,更不知道为什么。我做的测试应该可以帮助我诊断它。无法取消冻结。我知道。我想要的是将 freeze 转换为一个很好的可捕获异常,然后捕获它,然后记录它。但是当try / catch 没有按预期工作时(是的,即使在VS之外),我很无奈。

既然已经回答...

我制作了一个方便的工具,让我下次需要测试代码是否冻结时更轻松:

using System.Threading;
using System.Threading.Tasks;

namespace Woof.DebugEx {

    public class FreezeTest {

        private readonly Action Subject;
        private int Timeout;
        private bool IsDone;

        public FreezeTest(Action subject) => Subject = subject;

        public void Test(int timeout) {
            Timeout = timeout;
            var tested = new Task(Tested, null, TaskCreationOptions.LongRunning);
            var watchdog = new Task(WatchDog, null, TaskCreationOptions.LongRunning);
            tested.Start();
            watchdog.Start();
            Task.WaitAny(tested, watchdog);
            if (!IsDone) throw new TimeoutException("FREEZE DETECTED.");
        }

        private void Tested(object state) {
            Subject();
            IsDone = true;
        }

        private void WatchDog(object state) => Thread.Sleep(Timeout);

    }

}

现在我的测试片段如下所示:

    Console.Write("Testing");
    try {
        for (int j = 0; j < 8; j++) {
            Console.Write(".");
            for (int i = 0; i < 16; i++) {
                new FreezeTest(() => {
                    clients.Add(LDSCheckAsync().Result);
                }).Test(100);
            }
            clients.ForEach(i => i.Close());
            clients.Clear();
            Thread.Sleep(1000);
        }
        Console.WriteLine("OK.");
    }
    catch {
        clients.ForEach(i => i?.Dispose());
        Console.WriteLine("FAIL!");
    }

【问题讨论】:

  • 什么是未处理的异常,是哪一行抛出的?
  • 您能否提供有关未处理异常的信息?它在堆栈中的哪个位置被抛出?
  • @David:正好在“throw”行,没有调用堆栈。
  • @vtorola 正是它。除了明显的单线程片段。实际上它不是单线程的。主线程外抛出异常。

标签: c# unit-testing


【解决方案1】:

你可以这样做:

for (int i = 0; i < 256; i++) {
    var check = LDSCheckAsync();
    var idx = Task.WaitAny(Task.Delay(500), check);
    if (idx == 0) {
        throw new TimeoutException("FREEZE DETECTED");
    }
    else if (idx == 1) {
         var result = check.Result;
         // do something with it
    }
}

您当前方法失败的原因是CancellationToken.Register 的延续在另一个线程上运行,因此您的catch 块无法真正捕获它(并且它无论如何都无法在当前线程上运行,因为它被Result 调用阻塞)。

注意Task.WaitAny即使你的任务失败也不会抛出任何异常,所以你应该期望在检查结果时会抛出异常。另请记住,在您抛出超时异常后 - 您的主要任务仍在运行,并且将来可能会失败,这可能会再次使您的应用程序崩溃(取决于我记得的框架版本)。因此,最好为您的主要任务注册延续并在那里发现异常,即使此时您不关心它的结果。像这样的:

if (idx == 0) {
    check.ContinueWith(t =>
    {
         // observe, maybe do something like logging it
         var e = t.Exception;
    }, TaskContinuationOptions.OnlyOnFaulted);
    throw new TimeoutException("FREEZE DETECTED");
}

【讨论】:

  • 哇老兄!当然!工作。
猜你喜欢
  • 2017-02-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-01
  • 2018-12-13
  • 2012-05-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多