【问题标题】:NUnit does not fail on exception in FinalizerNUnit 在终结器中不会因异常而失败
【发布时间】:2015-08-20 09:37:01
【问题描述】:

在我们的框架中,有一些具有文件句柄或 WCF 客户端连接的关键对象。这些对象是IDiposable,我们有验证代码(抛出异常)以确保它们在不再需要时得到正确处理。 (仅调试,以便我们不想在发布时崩溃)。这不一定是关机。

除此之外,我们还有运行代码的单元测试,因此如果我们忘记了这些处理,我们预计它们会失败。

问题是:在.NET 4.5.1 上,当Finalizer 中的此类异常为扔了。

奇怪的是:使用NCrunch(也超过了NUnit),单元测试DO失败了! (这对我来说在本地,至少我可以找到这样丢失的处置)

这很糟糕,因为我们的构建机器(TeamCity)没有看到这样的失败,我们认为一切都很好!但是运行我们的软件(在调试中)确实会崩溃,表明我们忘记了处理

这里有一个例子表明 NUnit 没有失败

public class ExceptionInFinalizerObject
{
    ~ExceptionInFinalizerObject()
    {
        //Tried here both "Assert.Fail" and throwing an exception to be sure
        Assert.Fail();
        throw new Exception();
    }
}

[TestFixture]
public class FinalizerTestFixture
{
    [Test]
    public void FinalizerTest()
    {
        CreateFinalizerObject();

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    public void CreateFinalizerObject()
    {
        //Create the object in another function to put it out of scope and make it available for garbage collection
        new ExceptionInFinalizerObject();
    }
}

在 NUnit 运行器中运行它:一切都是绿色的。 让 ReSharper 调试这个测试确实会进入 Finalizer。

【问题讨论】:

  • 暂时不要使用终结器。假设您有一个创建新线程的 NUnit 测试,该线程运行抛出的代码。测试失败了吗? (不是反问;我不知道。)如果测试确实失败了,测试通过什么机制检测到另一个线程上的异常?如果它没有失败,那么当它是终结器线程时,为什么你会期望情况会有所不同?
  • 好点埃里克!你是对的,NUnit 不会在另一个线程上捕获异常!所以我找到了一些关于它的提示,并且会找到一个正确的方法来解决它!谢谢!

标签: c# .net nunit finalizer


【解决方案1】:

所以在 Eric Lippert 的帮助下,我发现 Exceptions 在另一个线程上时不会被 NUnit 捕获。所以同样的情况也发生在终结器线程上。

我尝试在 NUnit 的设置中寻找解决方案,但无济于事。

所以我想出了将我所有的TestFixture 子类化,这样我的所有测试就有一个共同的[SetUp][TearDown]

public class BaseTestFixture
{
    private UnhandledExceptionEventHandler _unhandledExceptionHandler;
    private bool _exceptionWasThrown;

    [SetUp]
    public void UnhandledExceptionRegistering()
    {
        _exceptionWasThrown = false;
        _unhandledExceptionHandler = (s, e) =>
        {
            _exceptionWasThrown = true;
        };

        AppDomain.CurrentDomain.UnhandledException += _unhandledExceptionHandler;
    }

    [TearDown]
    public void VerifyUnhandledExceptionOnFinalizers()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();

        Assert.IsFalse(_exceptionWasThrown);

        AppDomain.CurrentDomain.UnhandledException -= _unhandledExceptionHandler;
    }
}

显然,使用这段代码我只能知道抛出了异常,但我不知道是哪一个。但是,对于我的使用,这已经足够了。如果我稍后更改它,我会尝试更新这个(或者如果有人有更好的解决方案,我很高兴设置为解决方案!)

我有两个场景需要介绍,所以我将它们包括在这里:

[TestFixture]
public class ThreadExceptionTestFixture : BaseTestFixture
{
    [Test, Ignore("Testing-Testing test: Enable this test to validate that exception in threads are properly caught")]
    public void ThreadExceptionTest()
    {
        var crashingThread = new Thread(CrashInAThread);
        crashingThread.Start();
        crashingThread.Join(500);
    }

    private static void CrashInAThread()
    {
        throw new Exception();
    }

    [Test, Ignore("Testing-Testing test: Enable this test to validate that exceptions in Finalizers are properly caught")]
    public void FinalizerTest()
    {
        CreateFinalizerObject();

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    public void CreateFinalizerObject()
    {
        //Create the object in another function to put it out of scope and make it available for garbage collection
        new ExceptionInFinalizerObject();
    }
}

public class ExceptionInFinalizerObject
{
    ~ExceptionInFinalizerObject()
    {
        throw new Exception();
    }
}

至于为什么 NCrunch 做得好,这是个好问题……

【讨论】:

  • 好吧,您可以简单地存储异常本身来报告它,而不是保留布尔标志 (_exceptionWasThrown)。
【解决方案2】:

终结者中的例外情况不同,请参阅c# finalizer throwing exception?

在早期的 .Net 中,它们被忽略了。在较新的版本中,CLR 退出并出现致命错误。

【讨论】:

  • 好消息,我添加了我正在使用的 .Net 版本,因为它似乎很重要(我在 4.5.1)
【解决方案3】:

quote Eric Lippert(谁知道这件事和几乎任何人一样多):

如果您想保证所有终结器都已运行,请在调用 Collect 之后调用恰当命名的 WaitForPendingFinalizers 。这将暂停当前线程,直到终结器线程开始清空队列。如果您想确保这些最终对象的内存被回收,那么您将不得不调用Collect 一次 次。 [强调添加]

在不同环境中运行时的不一致行为恰恰凸显了预测 GC 行为的难度。有关垃圾收集的更多信息,请参阅 Raymond Chen 的文章:

或 Eric 的博客条目:

【讨论】:

  • 很遗憾,在哭了很久之后,我已经尝试了 3 次排队 GC.Collect(); GC.WaitForPendingFinalizers();,甚至在他们之间添加了延迟,看看是否有任何帮助
猜你喜欢
  • 1970-01-01
  • 2015-10-02
  • 2020-05-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-29
  • 2020-11-14
  • 2018-03-12
  • 2014-07-29
相关资源
最近更新 更多