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

时间:2012-09-27 21:07:25

标签: c# multithreading exception-handling deadlock

鉴于:

  • 在终结器中取锁会导致死锁
  • 终结者可以抛出异常

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

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 个答案:

答案 0 :(得分:3)

  

鉴于:

     
      
  • 在终结器中取锁会导致死锁
  •   
  • 终结者可以抛出异常
  •   

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

  

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

     

另见c# finalizer throwing exception?

注意:即使异常可能源自函数内部,但这并不意味着它将在该函数的上下文中处理。事实上,堆栈将被解开:

  

doc: 只有在线程的整个堆栈已经展开而没有找到适用的异常处理程序时才会处理异常,因此可以引发事件的第一个位置是应用程序域中的线程起源。


我不明白为什么锁定不安全。 (通常的警告将适用:不要在持锁时进行阻塞操作......)。

但是,您可能需要在此重复考虑重入和无限递归:

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

    - >如果重入是不允许的(或者需要特殊的操作(比如,在其他地方记录),你需要一个明确的'inLoggingOperation'标志,即使获得了锁,因为锁阻止了单线程的重入

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

答案 1 :(得分:0)

好吧,我做了一些狩猎,我在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方法锁定而不会死锁。如果有人不知道我会喜欢他们(并且参考也会很好)。

答案 2 :(得分:0)

为后代...一些测试代码:

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,因为应用程序离开主输出读取:

in handler -- pre lock

但是如果由于GC.Collect / WaitForPending终结器它的最终化,它会读取:

in handler -- pre lock
got event
in handler

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

在其他所有测试中,我都可以认为一切都按预期发生,threadProc唤醒并进行记录。