异常处理是代码中很重要的一项注意点。但是有时候一些不恰当的异常处理,反而会影响我们在代码运行时的调试。


出现问题

假如我们有这么一段代码,我期望调用Bar方法,输出“黄腾霄是帅哥”。

但是在实际运行中,其中的一个方法Foo抛出了一个异常。

而作为代码书写者,为了确保软件的正常运行,将这个方法的所有异常都吞掉了。

此时导致的结果就是,用户端看到软件运行正常,但是没有期望的输出

		static void Main(string[] args)
        {
            Bar();
        }

        static void Bar()
        {
            try
            {
                Foo();
                Console.WriteLine("黄腾霄是帅哥");
            }
            catch (Exception)
            {
                // ignored
            }
        }

        static void Foo()
        {
            throw new InvalidOperationException("黄腾霄是逗比");
        }

修改代码

此时就需要使用我们这篇博客的主角——AppDomain.FirstChanceException

我们试着对代码进行如下修改

		private static int n = 1;

        static void Main(string[] args)
        {
            AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
            {
                 Console.WriteLine(eventArgs.Exception);
            };
            Bar();
        }

        static void Bar()
        {
            try
            {
                Foo();
                Console.WriteLine("黄腾霄是帅哥");
            }
            catch (Exception)
            {
                // ignored
            }
        }

        static void Foo()
        {
            throw new InvalidOperationException("黄腾霄是逗比");
        }

我们可以看到异常信息被输出到屏幕了,并且此时程序仍然正常运行,没有出现奔溃

1563002057716

什么是FirstChanceException

FirstChanceException是一个异常通知事件。当一个AppDomain中引发任何异常的时候,CLR会优先触发FirstChanceException, 当其被处理后,才会开始寻找原始异常的处理函数。

例如我们在Barcatch块中添加一些日志输出,会发现这些输出出现在FirstChanceException的输出之后

		static void Bar()
        {
            try
            {
                Foo();
                Console.WriteLine("黄腾霄是帅哥");
            }
            catch (Exception)
            {
                Console.WriteLine("不许你侮辱我家男神");
            }
        }

1563002948322

对于FirstChanceException,需要注意的是它只是一个通知。它会在任何引发异常的地方(throw)的地方,触发事件。但是它并不能作为异常统一处理的地方。

思考

  • 假如我们在FirstChanceException的处理函数中抛出一个异常会怎么样?

请你绝对不要做出这种事情。我们刚刚说了FirstChanceException会在任何引发异常的地方触发该事件,如果你在FirstChanceException的处理函数中再次抛出异常,即使使用了trycatch,也会出现死循环,最终导致stackoverflow。

所以不能够在这个方法里面写出任何可能出现异常的代码。

  • 使用诸如throw;这样的语句是否会对同一个异常再次触发FirstChanceException?

参见下面这个例子,请问我被侮辱了几次?

		static void Main(string[] args)
        {
            AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) =>
            {
                Console.WriteLine($"黄腾霄被侮辱次数+{n++}");
            };
            try
            {
                try
                {
                    try
                    {
                        throw new InvalidOperationException("黄腾霄是逗比");
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException("不许你侮辱我家男神", e);
                    }
                }
                catch (Exception e)
                {
                    ExceptionDispatchInfo.Capture(e).Throw();
                }
            }
            catch (Exception e)
            {
                throw;
            }
        }

答案是4次。实际上FirstChanceException是每次引发异常的地方都会触发。而无论是 ExceptionDispatchInfo.Capture(e).Throw();还是throw;都只是保证了该异常的堆栈可以连续,而这个异常仍然会被重新引发一次。

1563005071979

参考链接:


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/FirstChanceException%E5%8E%9F%E7%90%86.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系