try/catch块中的C#未处理异常,检测到冻结

Har*_*rry 3 c# unit-testing

目标:对未经测试的外部代码执行超时.如果我的应用程序会导致另一方冻结,我想立即知道.让我们说这是非常复杂的单元测试.如果测试客户端使用我的应用程序并在500毫秒内返回 - 它已通过,如果没有 - 它失败了.

所以,是的,我不会对客户端温和,这是一个崩溃测试网站.我等了500ms,然后我不在乎客户端等待什么,我想立即将它与异常崩溃,但那么报告它作为测试结果.

可能是许多测试结果之一.

那么,现在,这里,代码:

    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!");
    }
Run Code Online (Sandbox Code Playgroud)

我设计的测试失败了.但我没有得到"失败"的消息,我得到了UNHANDLED EXCEPTION.

为什么,以及如何正确地做到这一点.在你尝试写评论之前,请真的考虑我不能IfCancellationRequested在这里使用或类似的东西.测试下的功能通常会立即退出,但由于某些网络错误,它可以进入30秒冻结.当它发生时我完全不知道,为什么更为未知.我做的测试应该帮助我诊断它.冻结不能取消.我知道.我想要的是将冻结转换为一个很好的捕获异常,然后捕获它,然后记录它.但是当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);

    }

}
Run Code Online (Sandbox Code Playgroud)

现在我的测试片段看起来像这样:

    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!");
    }
Run Code Online (Sandbox Code Playgroud)

Evk*_*Evk 8

你可以这样做:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

你当前的方法失败的原因是因为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");
}
Run Code Online (Sandbox Code Playgroud)