目标:对未经测试的外部代码发出超时。如果我的应用程序会导致另一方冻结,我想立即知道。让我们说它是非常复杂的单元测试。如果测试客户端使用我的应用程序并在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!");
}
我设计的测试失败了。但我没有得到&#34;失败&#34;消息,我得到了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);
}
}
现在我的测试片段如下所示:
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!");
}
答案 0 :(得分: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
}
}
你当前的方法失败的原因是因为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");
}