2

鉴于:

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

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

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
    }
}
4

3 回答 3

3

鉴于:

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

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

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

另请参阅c# 终结器抛出异常?

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

doc: 只有在未找到适用的异常处理程序的情况下解开线程的整个堆栈时才未处理异常,因此可以引发事件的第一个位置是在线程发起的应用程序域中。


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

但是,您可能需要在此处对可重入和无限递归三思而后行:

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

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

  • 次要观点:如果您的日志记录不是完全防异常的,那么当您已经在 CurrentDomain.UnhandledException 中时,您可能会遇到麻烦(AFAICT文档没有描述在事件处理程序中引发异常时会发生什么)。

于 2012-09-27T21:43:45.653 回答
0

好吧,我做了一些搜索,我从 MSDN 中找到了这个:

形式的锁定语句

lock (x) ...

其中x是引用类型的表达式,精确地等价于

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

除了x只评估一次。

在持有互斥锁的同时,在同一个执行线程中执行的代码也可以获得和释放锁。但是,在释放锁之前,其他线程中执行的代码将无法获得锁。

8.12 锁语句

finally因此,如果由于该语句而从锁内抛出异常,则锁将被释放。

有了这些信息,我 95% 确信你不会因为尝试从你的CurrentDomain_UnhandledException方法中锁定而死锁。如果有人知道,我很乐意从他们那里得到(参考也很好)。

于 2012-09-27T21:43:19.460 回答
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 因为应用程序离开 main 而最终确定,则输出将显示:

in handler -- pre lock

但是,如果由于 GC.Collect/WaitForPending 终结器而将其终结,则显示为:

in handler -- pre lock
got event
in handler

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

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

于 2012-09-28T05:20:34.683 回答