代码之家  ›  专栏  ›  技术社区  ›  Yaur

锁定未处理的异常处理程序是否安全?

  •  2
  • Yaur  · 技术社区  · 12 年前

    鉴于:

    • 在终结器中获取锁可能会导致死锁
    • 终结器可以引发异常

    在未处理的异常处理程序中获取锁是否安全,或者下面的代码是否会导致死锁?

    static void Main(string[] args)
    {
         AppDomain.CurrentDomain.UnhandledException += 
             new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
         //do other stuff
    }
    
    private static object loggingLock = new object();
    
    static void CurrentDomain_UnhandledException(
        object sender, 
        UnhandledExceptionEventArgs e)
    {
        lock (loggingLock)
        {
            //log the exception
        }
    }
    
    3 回复  |  直到 12 年前
        1
  •  3
  •   Community Dai    4 年前

    鉴于:

    • 在终结器中获取锁可能会导致死锁
    • 终结器可以引发异常

    编辑 事实证明,终结器中抛出的异常根据定义是致命的:

    doc: 如果Finalize或Finalize的覆盖引发异常,并且运行时不是由覆盖默认策略的应用程序托管的,则运行时将终止进程,并且不会执行活动的try-finaly块或终结器。如果终结器无法释放或销毁资源,则此行为可确保进程的完整性。

    另请参阅 c# finalizer throwing exception?

    笔记 :即使异常可能起源于函数内部,但这并不意味着它将在该函数的上下文中处理。事实上,堆叠本应被解开:

    doc: 只有在线程的整个堆栈都已展开而未找到适用的异常处理程序的情况下,才会对异常进行未处理,因此可以首先在线程所在的应用程序域中引发事件。


    我不明白为什么锁起来不安全。(通常需要注意的是:持有锁时不要进行阻塞操作…)。

    然而,您可能需要在这里再三考虑可重入性和无限递归:

    • 你会如何应对错误 日志记录时 ?锁将被获取 根据定义 ,因为线程已经持有它。但是日志记录代码是可重入的吗?即:是否会调用另一个 日志 正在进行的(失败/失败)操作的操作混乱状态?日志记录是否可能?

      -->如果不允许重入(或需要特殊操作(如在其他地方登录),则即使获取了锁,也需要显式的“inLoggingOperation”标志,因为锁不会阻止单线程重入

    • 次要的一点:如果你的日志记录不完全是异常证明,那么当你已经在CurrentDomain.UnhandledException(AFAICT)中时,你可能会遇到麻烦 the docs do not describe 当在事件处理程序中引发异常时会发生什么)。

        2
  •  0
  •   Matt Klein    12 年前

    嗯,我找了一些东西,从MSDN上找到了这个:

    形式的锁语句

    lock (x) ...
    

    哪里 x 是引用类型的表达式,精确地等效于

    System.Threading.Monitor.Enter(x);
    try {
       ...
    }
    finally {
       System.Threading.Monitor.Exit(x);
    }
    

    除了 x 仅评估一次。

    当持有互斥锁时,在同一执行线程中执行的代码也可以获得并释放该锁。但是,在释放锁之前,在其他线程中执行的代码被阻止获取锁。

    8.12 The lock statement

    因此,如果由于 finally 陈述

    有了这些信息,我将有95%的把握,你不会因为试图从你的 CurrentDomain_UnhandledException 方法如果有人知道其他情况,我很乐意从他们那里来(参考资料也很好)。

        3
  •  0
  •   Yaur    12 年前

    为子孙后代。。。一些测试代码:

    class Program
    {
        static AutoResetEvent e1 = new AutoResetEvent(false);
        static AutoResetEvent e2 = new AutoResetEvent(false);
        private static object lockObject = new object();
    
        private static void threadProc()
        {
            lock (lockObject)
            {
                e1.Set();
                e2.WaitOne();
                Console.WriteLine("got event");
            }
        }
    
        private static int finalized = 0;
    
        public class finalizerTest
        {
    
            ~finalizerTest()
            {
                try
                {
                    throw new NullReferenceException();
                }
                finally
                {
                    Interlocked.Increment(ref finalized);
                }
            }
        }
    
        static void Main(string[] args)
        {
            ThreadPool.QueueUserWorkItem((a) => threadProc());
            e1.WaitOne();
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            {
                finalizerTest f = new finalizerTest();
            }
    
            //uncommenting this will cause logging to happen as expected
            /*
            while (finalized == 0)
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
            }
             */
    
        }
    
        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Console.WriteLine("in handler -- pre lock");
            e2.Set();
    
            lock (lockObject)
            {
                Console.WriteLine("in handler");
            }
        }
    }
    

    发生的情况是,如果finalizerTest因为应用程序离开main而最终确定,则输出为:

    in handler -- pre lock
    

    但是,如果它由于GC.Collect/WWaitForPending终结器而被终结,则它读取:

    in handler -- pre lock
    got event
    in handler
    

    这意味着,在应用程序关闭时终结器抛出异常的特定情况下,您可能无法获得锁,但在这种情况下,应用程序和终结队列已经出现严重问题,锁定并不会使情况变得更糟。

    在其他每一次测试中,我都能想到一切都如预期的那样发生, threadProc 唤醒并进行日志记录。