控制台.WriteLine影响异常堆栈

本文关键字:堆栈 异常 影响 WriteLine 控制台 | 更新日期: 2023-09-27 18:13:32

我有以下代码:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Started");
        var res = GetSlowStringAsync();
        res.ContinueWith(
            c =>
                {
                    Console.WriteLine("Will it crash?");
                    //Console.WriteLine(c.Result);
                }
            );
        //Console.WriteLine("Press any key");
        Console.ReadKey();
        Console.WriteLine("Continue on the main thread");
        GC.Collect();
        Console.WriteLine("Memory cleared");
        Thread.Sleep(10000);
    }
    public static async Task<string> GetSlowStringAsync()
    {
        await Task.Delay(2000);
        throw new Exception("will you handle it?");
        return "somestring";
    }
}

也在App.config中添加了以下行:

<runtime>
    <ThrowUnobservedTaskExceptions enabled ="true" />
</runtime>

我使用visual studio 2015 14.0.23107.0 D14REL目标。net Framework 4.6。

在"不调试启动"模式下执行。

如果在问题"Will it crash"后按任意按钮,则程序将崩溃。

但是如果取消注释

Console.WriteLine("Press any key");

并在"不调试运行"模式下执行,这样程序就不会崩溃。为什么控制台。WriteLine影响引发异常的方式?

控制台.WriteLine影响异常堆栈

我非常怀疑是否有人能从你的代码片段重现这个问题。我的水晶球告诉我,您更改了程序,在程序末尾添加了Thread.Sleep()。并且隐藏了原始程序的线程竞争bug。假设call不存在,我将写下这个答案。

只有当所有条件都为真时,才会抛出异常:

  • 运行程序的发布版本
  • 运行程序时不附带调试器
  • GC.Collect()调用实际上是对res对象进行垃圾回收。如果不满足前两个条件,则不会出现这种情况在main()方法结束之前,GC.Collect()方法结束之后,终结器线程有足够的时间来完成它的工作。Console.WriteLine()调用可以给它足够的时间来运行TaskExceptionHolder终结器。不能保证,线程竞争bug的问题。

改变结果的方法是工具>选项>调试>常规>取消"抑制JIT优化"选项。这样可以确保结果不受附加调试器的影响。并且在GC.Collect()调用之前添加res = null;或将代码移动到另一个方法中,从而确保即使在调试构建中也可以收集res。为什么GC.Collect()的行为如此不可预测,在这篇文章中解释了。

为了理解这种行为,您需要知道的唯一一件事是,当程序忙于关闭时,异常被抑制。注意环境的使用。TaskExceptionHolder结束器中的HasShutdownStarted和AppDomain.CurrentDomain.IsFinalizingForUnload属性。因此,如果终结器线程在完成它的工作时有点慢,那么在终结器运行的时候,shutdown可能已经开始了,你不会得到异常。在GC.Collect()调用之后添加到Main()中的任何内容,如Console.WriteLine(),都可以提高看到异常的几率。

添加GC.WaitForPendingFinalizers()解决了线程竞争错误,并使结果可预测。嗯,更容易预测:)